Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.CodingConvention.Bind;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.regex.Pattern;
/**
* A peephole optimization that minimizes code by simplifying conditional
* expressions, replacing IFs with HOOKs, replacing object constructors
* with literals, and simplifying returns.
*
*/
class PeepholeSubstituteAlternateSyntax
extends AbstractPeepholeOptimization {
private static final int AND_PRECEDENCE = NodeUtil.precedence(Token.AND);
private static final int OR_PRECEDENCE = NodeUtil.precedence(Token.OR);
private static final int NOT_PRECEDENCE = NodeUtil.precedence(Token.NOT);
private final boolean late;
private final int STRING_SPLIT_OVERHEAD = ".split('.')".length();
static final DiagnosticType INVALID_REGULAR_EXPRESSION_FLAGS =
DiagnosticType.error(
"JSC_INVALID_REGULAR_EXPRESSION_FLAGS",
"Invalid flags to RegExp constructor: {0}");
static final Predicate<Node> DONT_TRAVERSE_FUNCTIONS_PREDICATE
= new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return !input.isFunction();
}
};
/**
* @param late When late is false, this mean we are currently running before
* most of the other optimizations. In this case we would avoid optimizations
* that would make the code harder to analyze (such as using string splitting,
* merging statements with commas, etc). When this is true, we would
* do anything to minimize for size.
*/
PeepholeSubstituteAlternateSyntax(boolean late) {
this.late = late;
}
/**
* Tries apply our various peephole minimizations on the passed in node.
*/
@Override
@SuppressWarnings("fallthrough")
public Node optimizeSubtree(Node node) {
switch(node.getType()) {
case Token.RETURN: {
Node result = tryRemoveRedundantExit(node);
if (result != node) {
return result;
}
result = try
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>ReplaceExitWithBreak(node);
if (result != node) {
return result;
}
return tryReduceReturn(node);
}
case Token.THROW: {
Node result = tryRemoveRedundantExit(node);
if (result != node) {
return result;
}
return tryReplaceExitWithBreak(node);
}
// TODO(johnlenz): Maybe remove redundant BREAK and CONTINUE. Overlaps
// with MinimizeExitPoints.
case Token.NOT:
tryMinimizeCondition(node.getFirstChild());
return tryMinimizeNot(node);
case Token.IF:
tryMinimizeCondition(node.getFirstChild());
return tryMinimizeIf(node);
case Token.EXPR_RESULT:
tryMinimizeCondition(node.getFirstChild());
return node;
case Token.HOOK:
tryMinimizeCondition(node.getFirstChild());
return node;
case Token.WHILE:
case Token.DO:
tryMinimizeCondition(NodeUtil.getConditionExpression(node));
return node;
case Token.FOR:
if (!NodeUtil.isForIn(node)) {
tryJoinForCondition(node);
tryMinimizeCondition(NodeUtil.getConditionExpression(node));
}
return node;
case Token.TRUE:
case Token.FALSE:
return reduceTrueFalse(node);
case Token.NEW:
node = tryFoldStandardConstructors(node);
if (!node.isCall()) {
return node;
}
// Fall through on purpose because tryFoldStandardConstructors() may
// convert a NEW node into a CALL node
case Token.CALL:
Node result = tryFoldLiteralConstructor(node);
if (result == node) {
result = tryFoldSimpleFunctionCall(node);
if (result == node) {
result = tryFoldImmediateCallToBoundFunction(node);
}
}
return result;
case Token.COMMA:
return trySplitComma(node);
case Token.NAME:
return tryReplaceUndefined(node);
case Token.BLOCK:
return tryReplaceIf(node);
case Token.ARRAYLIT:
return tryMinimizeArrayLiteral(node);
default:
return node; //Nothing changed
}
}
private void tryJoinForCondition(Node n) {
if (!late) {
return;
}
Node block = n.getLastChild();
Node maybeIf = block.getFirstChild();
if (maybeIf != null && maybeIf.isIf()) {
Node maybeBreak = maybeIf.getChildAtIndex(1).getFirstChild();
if (maybeBreak != null && maybeBreak.isBreak()
&& !maybeBreak.hasChildren()) {
// Preserve the IF ELSE expression is there is one.
if (maybeIf.getChildCount() == 3) {
block.replaceChild(maybeIf,
maybeIf.getLastChild().detachFromParent());
} else {
block.removeFirstChild();
}
Node ifCondition = maybeIf.removeFirstChild();
Node fixedIfCondition = IR.not(ifCondition)
.srcref(ifCondition);
// OK, join the IF expression with the FOR expression
Node forCondition = NodeUtil.getConditionExpression(n);
if (forCondition.isEmpty()) {
n.replaceChild(forCondition, fixed
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>IfCondition);
} else {
Node replacement = new Node(Token.AND);
n.replaceChild(forCondition, replacement);
replacement.addChildToBack(forCondition);
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
Bind bind = getCodingConvention().describeFunctionBind(callTarget, false);
if (bind != null) {
// replace the call target
bind.target.detachFromParent();
n.replaceChild(callTarget, bind.target);
callTarget = bind.target;
// push the parameters
addParameterAfter(bind.parameters, callTarget);
// add the this value before the parameters if necessary
if (bind.thisValue != null && !NodeUtil.isUndefined(bind.thisValue)) {
// rewrite from "fn(a, b)" to "fn.call(thisValue, a, b)"
Node newCallTarget = IR.getprop(
callTarget.cloneTree(),
IR.string("call").srcref(callTarget));
n.replaceChild(callTarget, newCallTarget);
n.addChildAfter(bind.thisValue.cloneTree(), newCallTarget);
n.putBooleanProp(Node.FREE_CALL, false);
} else {
n.putBooleanProp(Node.FREE_CALL, true);
}
reportCodeChange();
}
return n;
}
private void addParameterAfter(Node parameterList, Node after) {
if (parameterList != null) {
// push the last parameter to the head of the list first.
addParameterAfter(parameterList.getNext(), after);
after.getParent().addChildAfter(parameterList.cloneTree(), after);
}
}
private Node trySplitComma(Node n) {
if (late) {
return n;
}
Node parent = n.getParent();
Node left = n.getFirstChild();
Node right = n.getLastChild();
if (parent.isExprResult()
&& !parent.getParent().isLabel()) {
// split comma
n.detachChildren();
// Replace the original expression with the left operand.
parent.replaceChild(n, left);
// Add the right expression afterward.
Node newStatement = IR.exprResult(right);
newStatement.copyInformationFrom(n);
//This modifies outside the subtree, which is not
//desirable in a peephole optimization.
parent.getParent().addChildAfter(newStatement, parent);
reportCodeChange();
return left;
} else {
return n;
}
}
/**
* Use "return x?1:2;" in place of "if(x)return 1;return 2;"
*/
private Node tryReplaceIf(Node n) {
for (Node child = n.getFirstChild();
child != null; child = child.getNext()){
if (child.isIf()){
Node cond = child.getFirstChild();
Node thenBranch = cond.getNext();
Node elseBranch = thenBranch.getNext();
Node nextNode = child.getNext();
if (nextNode != null && elseBranch == null
&& isReturnBlock(thenBranch)
&& nextNode.isIf()) {
Node nextCond = nextNode.getFirstChild();
Node nextThen = nextCond.getNext();
Node next
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>Else = nextThen.getNext();
if (thenBranch.isEquivalentToTyped(nextThen)) {
// Transform
// if (x) return 1; if (y) return 1;
// to
// if (x||y) return 1;
child.detachFromParent();
child.detachChildren();
Node newCond = new Node(Token.OR, cond);
nextNode.replaceChild(nextCond, newCond);
newCond.addChildToBack(nextCond);
reportCodeChange();
} else if (nextElse != null
&& thenBranch.isEquivalentToTyped(nextElse)) {
// Transform
// if (x) return 1; if (y) foo() else return 1;
// to
// if (!x&&y) foo() else return 1;
child.detachFromParent();
child.detachChildren();
Node newCond = new Node(Token.AND,
IR.not(cond).srcref(cond));
nextNode.replaceChild(nextCond, newCond);
newCond.addChildToBack(nextCond);
reportCodeChange();
}
} else if (nextNode != null && elseBranch == null &&
isReturnBlock(thenBranch) && isReturnExpression(nextNode)) {
Node thenExpr = null;
// if(x)return; return 1 -> return x?void 0:1
if (isReturnExpressBlock(thenBranch)) {
thenExpr = getBlockReturnExpression(thenBranch);
thenExpr.detachFromParent();
} else {
thenExpr = NodeUtil.newUndefinedNode(child);
}
Node elseExpr = nextNode.getFirstChild();
cond.detachFromParent();
elseExpr.detachFromParent();
Node returnNode = IR.returnNode(
IR.hook(cond, thenExpr, elseExpr)
.srcref(child));
n.replaceChild(child, returnNode);
n.removeChild(nextNode);
reportCodeChange();
} else if (elseBranch != null && statementMustExitParent(thenBranch)) {
child.removeChild(elseBranch);
n.addChildAfter(elseBranch, child);
reportCodeChange();
}
}
}
return n;
}
private boolean statementMustExitParent(Node n) {
switch (n.getType()) {
case Token.THROW:
case Token.RETURN:
return true;
case Token.BLOCK:
if (n.hasChildren()) {
Node child = n.getLastChild();
return statementMustExitParent(child);
}
return false;
// TODO(johnlenz): handle TRY/FINALLY
case Token.FUNCTION:
default:
return false;
}
}
/**
* Use "void 0" in place of "undefined"
*/
private Node tryReplaceUndefined(Node n) {
// TODO(johnlenz): consider doing this as a normalization.
if (isASTNormalized()
&& NodeUtil.isUndefined(n)
&& !NodeUtil.isLValue(n)) {
Node replacement = NodeUtil.newUndefinedNode(n);
n.getParent().replaceChild(n, replacement);
reportCodeChange();
return replacement;
}
return n;
}
/**
* Reduce "return undefined" or "
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>, the node can be removed.
* For example:
* "if (a) {return f()} return f();" ==> "if (a) {} return f();"
* "if (a) {throw 'ow'} throw 'ow';" ==> "if (a) {} throw 'ow';"
*
* @param n An follow control exit expression (a THROW or RETURN node)
* @return The replacement for n, or the original if no change was made.
*/
private Node tryRemoveRedundantExit(Node n) {
Node exitExpr = n.getFirstChild();
Node follow = ControlFlowAnalysis.computeFollowNode(n);
// Skip pass all the finally blocks because both the fall through and return
// will also trigger all the finally blocks.
Node prefinallyFollows = follow;
follow = skipFinallyNodes(follow);
if (prefinallyFollows != follow) {
// There were finally clauses
if (!isPure(exitExpr)) {
// Can't replace the return
return n;
}
}
if (follow == null && (n.isThrow() || exitExpr != null)) {
// Can't complete remove a throw here or a return with a result.
return n;
}
// When follow is null, this mean the follow of a break target is the
// end of a function. This means a break is same as return.
if (follow == null || areMatchingExits(n, follow)) {
n.detachFromParent();
reportCodeChange();
return null;
}
return n;
}
/**
* @return Whether the expression does not produces and can not be affected
* by side-effects.
*/
boolean isPure(Node n) {
return n == null
|| (!NodeUtil.canBeSideEffected(n)
&& !NodeUtil.mayHaveSideEffects(n));
}
/**
* @return n or the node following any following finally nodes.
*/
Node skipFinallyNodes(Node n) {
while (n != null && NodeUtil.isTryFinallyNode(n.getParent(), n)) {
n = ControlFlowAnalysis.computeFollowNode(n);
}
return n;
}
/**
* Check whether one exit can be replaced with another. Verify:
* 1) They are identical expressions
* 2) If an exception is possible that the statements, the original
* and the potential replacement are in the same exception handler.
*/
boolean areMatchingExits(Node nodeThis, Node nodeThat) {
return nodeThis.isEquivalentTo(nodeThat)
&& (!isExceptionPossible(nodeThis)
|| getExceptionHandler(nodeThis) == getExceptionHandler(nodeThat));
}
boolean isExceptionPossible(Node n) {
// TODO(johnlenz): maybe use ControlFlowAnalysis.mayThrowException?
Preconditions.checkState(n.isReturn()
|| n.isThrow());
return n.isThrow()
|| (n.hasChildren()
&& !NodeUtil.isLiteralValue(n.getLastChild(), true));
}
Node getExceptionHandler(Node n) {
return ControlFlowAnalysis.getExceptionHandler(n);
}
/**
* Try to minimize NOT nodes such as !(x==y).
*
* Returns the replacement for n or the original
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> if no change was made
*/
private Node tryMinimizeNot(Node n) {
Node parent = n.getParent();
Node notChild = n.getFirstChild();
// negative operator of the current one : == -> != for instance.
int complementOperator;
switch (notChild.getType()) {
case Token.EQ:
complementOperator = Token.NE;
break;
case Token.NE:
complementOperator = Token.EQ;
break;
case Token.SHEQ:
complementOperator = Token.SHNE;
break;
case Token.SHNE:
complementOperator = Token.SHEQ;
break;
// GT, GE, LT, LE are not handled in this because !(x<NaN) != x>=NaN.
default:
return n;
}
Node newOperator = n.removeFirstChild();
newOperator.setType(complementOperator);
parent.replaceChild(n, newOperator);
reportCodeChange();
return newOperator;
}
/**
* Try turning IF nodes into smaller HOOKs
*
* Returns the replacement for n or the original if no replacement was
* necessary.
*/
private Node tryMinimizeIf(Node n) {
Node parent = n.getParent();
Node cond = n.getFirstChild();
/* If the condition is a literal, we'll let other
* optimizations try to remove useless code.
*/
if (NodeUtil.isLiteralValue(cond, true)) {
return n;
}
Node thenBranch = cond.getNext();
Node elseBranch = thenBranch.getNext();
if (elseBranch == null) {
if (isFoldableExpressBlock(thenBranch)) {
Node expr = getBlockExpression(thenBranch);
if (!late && isPropertyAssignmentInExpression(expr)) {
// Keep opportunities for CollapseProperties such as
// a.longIdentifier || a.longIdentifier = ... -> var a = ...;
// until CollapseProperties has been run.
return n;
}
if (cond.isNot()) {
// if(!x)bar(); -> x||bar();
if (isLowerPrecedenceInExpression(cond, OR_PRECEDENCE) &&
isLowerPrecedenceInExpression(expr.getFirstChild(),
OR_PRECEDENCE)) {
// It's not okay to add two sets of parentheses.
return n;
}
Node or = IR.or(
cond.removeFirstChild(),
expr.removeFirstChild()).srcref(n);
Node newExpr = NodeUtil.newExpr(or);
parent.replaceChild(n, newExpr);
reportCodeChange();
return newExpr;
}
// if(x)foo(); -> x&&foo();
if (isLowerPrecedenceInExpression(cond, AND_PRECEDENCE) &&
isLowerPrecedenceInExpression(expr.getFirstChild(),
AND_PRECEDENCE)) {
// One additional set of parentheses is worth the change even if
// there is no immediate code size win. However, two extra pair of
// {}, we would have to think twice. (unless we know for sure the
// we can further optimize its parent.
return n;
}
n.removeChild(cond);
Node and = IR.and(cond, expr.removeFirstChild()).srcref(n);
Node newExpr = NodeUtil.
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>newExpr(and);
parent.replaceChild(n, newExpr);
reportCodeChange();
return newExpr;
} else {
// Try to combine two IF-ELSE
if (NodeUtil.isStatementBlock(thenBranch) &&
thenBranch.hasOneChild()) {
Node innerIf = thenBranch.getFirstChild();
if (innerIf.isIf()) {
Node innerCond = innerIf.getFirstChild();
Node innerThenBranch = innerCond.getNext();
Node innerElseBranch = innerThenBranch.getNext();
if (innerElseBranch == null &&
!(isLowerPrecedenceInExpression(cond, AND_PRECEDENCE) &&
isLowerPrecedenceInExpression(innerCond, AND_PRECEDENCE))) {
n.detachChildren();
n.addChildToBack(
IR.and(
cond,
innerCond.detachFromParent())
.srcref(cond));
n.addChildrenToBack(innerThenBranch.detachFromParent());
reportCodeChange();
// Not worth trying to fold the current IF-ELSE into && because
// the inner IF-ELSE wasn't able to be folded into && anyways.
return n;
}
}
}
}
return n;
}
/* TODO(dcc) This modifies the siblings of n, which is undesirable for a
* peephole optimization. This should probably get moved to another pass.
*/
tryRemoveRepeatedStatements(n);
// if(!x)foo();else bar(); -> if(x)bar();else foo();
// An additional set of curly braces isn't worth it.
if (cond.isNot() && !consumesDanglingElse(elseBranch)) {
n.replaceChild(cond, cond.removeFirstChild());
n.removeChild(thenBranch);
n.addChildToBack(thenBranch);
reportCodeChange();
return n;
}
// if(x)return 1;else return 2; -> return x?1:2;
if (isReturnExpressBlock(thenBranch) && isReturnExpressBlock(elseBranch)) {
Node thenExpr = getBlockReturnExpression(thenBranch);
Node elseExpr = getBlockReturnExpression(elseBranch);
n.removeChild(cond);
thenExpr.detachFromParent();
elseExpr.detachFromParent();
// note - we ignore any cases with "return;", technically this
// can be converted to "return undefined;" or some variant, but
// that does not help code size.
Node returnNode = IR.returnNode(
IR.hook(cond, thenExpr, elseExpr)
.srcref(n));
parent.replaceChild(n, returnNode);
reportCodeChange();
return returnNode;
}
boolean thenBranchIsExpressionBlock = isFoldableExpressBlock(thenBranch);
boolean elseBranchIsExpressionBlock = isFoldableExpressBlock(elseBranch);
if (thenBranchIsExpressionBlock && elseBranchIsExpressionBlock) {
Node thenOp = getBlockExpression(thenBranch).getFirstChild();
Node elseOp = getBlockExpression(elseBranch).getFirstChild();
if (thenOp.getType() == elseOp.getType()) {
// if(x)a=1;else a=2; -> a=x?1:2;
if (NodeUtil.isAssignmentOp
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>(thenOp)) {
Node lhs = thenOp.getFirstChild();
if (areNodesEqualForInlining(lhs, elseOp.getFirstChild()) &&
// if LHS has side effects, don't proceed [since the optimization
// evaluates LHS before cond]
// NOTE - there are some circumstances where we can
// proceed even if there are side effects...
!mayEffectMutableState(lhs)) {
n.removeChild(cond);
Node assignName = thenOp.removeFirstChild();
Node thenExpr = thenOp.removeFirstChild();
Node elseExpr = elseOp.getLastChild();
elseOp.removeChild(elseExpr);
Node hookNode = IR.hook(cond, thenExpr, elseExpr).srcref(n);
Node assign = new Node(thenOp.getType(), assignName, hookNode)
.srcref(thenOp);
Node expr = NodeUtil.newExpr(assign);
parent.replaceChild(n, expr);
reportCodeChange();
return expr;
}
}
}
// if(x)foo();else bar(); -> x?foo():bar()
n.removeChild(cond);
thenOp.detachFromParent();
elseOp.detachFromParent();
Node expr = IR.exprResult(
IR.hook(cond, thenOp, elseOp).srcref(n));
parent.replaceChild(n, expr);
reportCodeChange();
return expr;
}
boolean thenBranchIsVar = isVarBlock(thenBranch);
boolean elseBranchIsVar = isVarBlock(elseBranch);
// if(x)var y=1;else y=2 -> var y=x?1:2
if (thenBranchIsVar && elseBranchIsExpressionBlock &&
getBlockExpression(elseBranch).getFirstChild().isAssign()) {
Node var = getBlockVar(thenBranch);
Node elseAssign = getBlockExpression(elseBranch).getFirstChild();
Node name1 = var.getFirstChild();
Node maybeName2 = elseAssign.getFirstChild();
if (name1.hasChildren()
&& maybeName2.isName()
&& name1.getString().equals(maybeName2.getString())) {
Node thenExpr = name1.removeChildren();
Node elseExpr = elseAssign.getLastChild().detachFromParent();
cond.detachFromParent();
Node hookNode = IR.hook(cond, thenExpr, elseExpr)
.srcref(n);
var.detachFromParent();
name1.addChildrenToBack(hookNode);
parent.replaceChild(n, var);
reportCodeChange();
return var;
}
// if(x)y=1;else var y=2 -> var y=x?1:2
} else if (elseBranchIsVar && thenBranchIsExpressionBlock &&
getBlockExpression(thenBranch).getFirstChild().isAssign()) {
Node var = getBlockVar(elseBranch);
Node thenAssign = getBlockExpression(thenBranch).getFirstChild();
Node maybeName1 = thenAssign.getFirstChild();
Node name2 = var.getFirstChild();
if (name2.hasChildren()
&& maybeName1.isName()
&& maybeName1.getString().equals(name2.getString())) {
Node thenExpr = thenAssign.getLastChild().detachFromParent();
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>
Node elseExpr = name2.removeChildren();
cond.detachFromParent();
Node hookNode = IR.hook(cond, thenExpr, elseExpr)
.srcref(n);
var.detachFromParent();
name2.addChildrenToBack(hookNode);
parent.replaceChild(n, var);
reportCodeChange();
return var;
}
}
return n;
}
/**
* Try to remove duplicate statements from IF blocks. For example:
*
* if (a) {
* x = 1;
* return true;
* } else {
* x = 2;
* return true;
* }
*
* becomes:
*
* if (a) {
* x = 1;
* } else {
* x = 2;
* }
* return true;
*
* @param n The IF node to examine.
*/
private void tryRemoveRepeatedStatements(Node n) {
Preconditions.checkState(n.isIf());
Node parent = n.getParent();
if (!NodeUtil.isStatementBlock(parent)) {
// If the immediate parent is something like a label, we
// can't move the statement, so bail.
return;
}
Node cond = n.getFirstChild();
Node trueBranch = cond.getNext();
Node falseBranch = trueBranch.getNext();
Preconditions.checkNotNull(trueBranch);
Preconditions.checkNotNull(falseBranch);
while (true) {
Node lastTrue = trueBranch.getLastChild();
Node lastFalse = falseBranch.getLastChild();
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.isBlock()) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
if (maybeExpr.isExprResult()) {
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
if (maybeExpr.getFirstChild().isCall()) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
if (calledFn.isGetElem()) {
return false;
} else if (calledFn.isGetProp() &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
}
}
return false;
}
/**
* @return The expression node.
*/
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
* @return Whether the node is a block with a single statement that is
* an return with or without an expression.
*/
private boolean isReturnBlock(Node n) {
if (n.isBlock()) {
if (n.hasOneChild()) {
Node first = n.getFirstChild();
return first.isReturn();
}
}
return false;
}
/**
* @return Whether the node is a block with a single statement that is
* an return.
*/
private boolean isReturnExpressBlock(Node n) {
if (n.isBlock()) {
if (n.hasOneChild()) {
Node first = n.getFirstChild();
if (first.isReturn()) {
return first.hasOneChild();
}
}
}
return false;
}
/**
* @return Whether the node is a single return statement.
*/
private boolean isReturnExpression(Node n) {
if (n.isReturn()) {
return n.hasOneChild();
}
return false;
}
/**
* @return The expression that is part of the return.
*/
private Node getBlockReturnExpression(Node n) {
Preconditions.checkState(isReturnExpressBlock(n));
return n.getFirstChild().getFirstChild();
}
/**
* @return Whether the node is a block with a single statement that is
* a VAR declaration of a single variable.
*/
private boolean isVarBlock(Node n) {
if (n.isBlock()) {
if (n.hasOneChild()) {
Node first = n.getFirstChild();
if (first.isVar()) {
return first.hasOneChild();
}
}
}
return false;
}
/**
* @return The var node.
*/
private Node getBlockVar(Node n) {
Preconditions.checkState(isVarBlock(n));
return n.getFirstChild();
}
/**
* Does a statement consume a 'dangling else'? A statement consumes
* a 'dangling else' if an 'else' token following the statement
* would be considered by the parser to be part of the statement.
*/
private boolean consumesDanglingElse(Node n) {
while (true) {
switch (n.getType()) {
case Token.IF:
if (n.getChildCount() < 3) {
return true;
}
// This IF node has no else clause.
n = n.getLastChild();
continue;
case Token.WITH:
case Token.WHILE:
case Token.FOR:
n = n.getLastChild();
continue;
default:
return false;
}
}
}
/**
* Does the expression contain an operator with lower precedence than
* the argument?
*/
private boolean isLowerPrecedenceInExpression(Node n,
final int precedence) {
Predicate<Node> isLowerPrecedencePredicate = new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return NodeUtil.precedence(input.getType()) < precedence;
}
};
return NodeUtil.has(n, isLowerPre
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>cedencePredicate,
DONT_TRAVERSE_FUNCTIONS_PREDICATE);
}
/**
* Whether the node type has lower precedence than "precedence"
*/
private boolean isLowerPrecedence(Node n, final int precedence) {
return NodeUtil.precedence(n.getType()) < precedence;
}
/**
* Whether the node type has higher precedence than "precedence"
*/
private boolean isHigherPrecedence(Node n, final int precedence) {
return NodeUtil.precedence(n.getType()) > precedence;
}
/**
* Does the expression contain a property assignment?
*/
private boolean isPropertyAssignmentInExpression(Node n) {
Predicate<Node> isPropertyAssignmentInExpressionPredicate =
new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return (input.isGetProp() &&
input.getParent().isAssign());
}
};
return NodeUtil.has(n, isPropertyAssignmentInExpressionPredicate,
DONT_TRAVERSE_FUNCTIONS_PREDICATE);
}
/**
* Try to minimize conditions expressions, as there are additional
* assumptions that can be made when it is known that the final result
* is a boolean.
*
* The following transformations are done recursively:
* !(x||y) --> !x&&!y
* !(x&&y) --> !x||!y
* !!x --> x
* Thus:
* !(x&&!y) --> !x||!!y --> !x||y
*
* Returns the replacement for n, or the original if no change was made
*/
private Node tryMinimizeCondition(Node n) {
Node parent = n.getParent();
switch (n.getType()) {
case Token.NOT:
Node first = n.getFirstChild();
switch (first.getType()) {
case Token.NOT: {
Node newRoot = first.removeFirstChild();
parent.replaceChild(n, newRoot);
reportCodeChange();
// No need to traverse, tryMinimizeCondition is called on the
// NOT children are handled below.
return newRoot;
}
case Token.AND:
case Token.OR: {
// !(!x && !y) --> x || y
// !(!x || !y) --> x && y
// !(!x && y) --> x || !y
// !(!x || y) --> x && !y
// !(x && !y) --> !x || y
// !(x || !y) --> !x && y
// !(x && y) --> !x || !y
// !(x || y) --> !x && !y
Node leftParent = first.getFirstChild();
Node rightParent = first.getLastChild();
Node left, right;
// Check special case when such transformation cannot reduce
// due to the added ()
// It only occurs when both of expressions are not NOT expressions
if (!leftParent.isNot()
&& !rightParent.isNot()) {
// If an expression has higher precedence than && or ||,
// but lower precedence than NOT, an additional () is needed
// Thus we do not preceed
int op_precedence = NodeUtil.precedence(first.getType());
if ((isLowerPrecedence(leftParent, NOT
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>_PRECEDENCE)
&& isHigherPrecedence(leftParent, op_precedence))
|| (isLowerPrecedence(rightParent, NOT_PRECEDENCE)
&& isHigherPrecedence(rightParent, op_precedence))) {
return n;
}
}
if (leftParent.isNot()) {
left = leftParent.removeFirstChild();
} else {
leftParent.detachFromParent();
left = IR.not(leftParent).srcref(leftParent);
}
if (rightParent.isNot()) {
right = rightParent.removeFirstChild();
} else {
rightParent.detachFromParent();
right = IR.not(rightParent).srcref(rightParent);
}
int newOp = (first.isAnd()) ? Token.OR : Token.AND;
Node newRoot = new Node(newOp, left, right);
parent.replaceChild(n, newRoot);
reportCodeChange();
// No need to traverse, tryMinimizeCondition is called on the
// AND and OR children below.
return newRoot;
}
default:
TernaryValue nVal = NodeUtil.getPureBooleanValue(first);
if (nVal != TernaryValue.UNKNOWN) {
boolean result = nVal.not().toBoolean(true);
int equivalentResult = result ? 1 : 0;
return maybeReplaceChildWithNumber(n, parent, equivalentResult);
}
}
// No need to traverse, tryMinimizeCondition is called on the NOT
// children in the general case in the main post-order traversal.
return n;
case Token.OR:
case Token.AND: {
Node left = n.getFirstChild();
Node right = n.getLastChild();
// Because the expression is in a boolean context minimize
// the children, this can't be done in the general case.
left = tryMinimizeCondition(left);
right = tryMinimizeCondition(right);
// Remove useless conditionals
// Handle four cases:
// x || false --> x
// x || true --> true
// x && true --> x
// x && false --> false
TernaryValue rightVal = NodeUtil.getPureBooleanValue(right);
if (NodeUtil.getPureBooleanValue(right) != TernaryValue.UNKNOWN) {
int type = n.getType();
Node replacement = null;
boolean rval = rightVal.toBoolean(true);
// (x || FALSE) => x
// (x && TRUE) => x
if (type == Token.OR && !rval ||
type == Token.AND && rval) {
replacement = left;
} else if (!mayHaveSideEffects(left)) {
replacement = right;
}
if (replacement != null) {
n.detachChildren();
parent.replaceChild(n, replacement);
reportCodeChange();
return replacement;
}
}
return n;
}
case Token.HOOK: {
Node condition = n.getFirstChild();
Node trueNode = n.getFirstChild().getNext();
Node falseNode = n.getLastChild();
// Because the expression is in a boolean context minimize
// the result children, this can't be done in the general case.
// The condition is handled in the general case in #
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>optimizeSubtree
trueNode = tryMinimizeCondition(trueNode);
falseNode = tryMinimizeCondition(falseNode);
// Handle four cases:
// x ? true : false --> x
// x ? false : true --> !x
// x ? true : y --> x || y
// x ? y : false --> x && y
Node replacement = null;
TernaryValue trueNodeVal = NodeUtil.getPureBooleanValue(trueNode);
TernaryValue falseNodeVal = NodeUtil.getPureBooleanValue(falseNode);
if (trueNodeVal == TernaryValue.TRUE
&& falseNodeVal == TernaryValue.FALSE) {
// Remove useless conditionals, keep the condition
condition.detachFromParent();
replacement = condition;
} else if (trueNodeVal == TernaryValue.FALSE
&& falseNodeVal == TernaryValue.TRUE) {
// Remove useless conditionals, keep the condition
condition.detachFromParent();
replacement = IR.not(condition);
} else if (trueNodeVal == TernaryValue.TRUE) {
// Remove useless true case.
n.detachChildren();
replacement = IR.or(condition, falseNode);
} else if (falseNodeVal == TernaryValue.FALSE) {
// Remove useless false case
n.detachChildren();
replacement = IR.and(condition, trueNode);
}
if (replacement != null) {
parent.replaceChild(n, replacement);
n = replacement;
reportCodeChange();
}
return n;
}
default:
// while(true) --> while(1)
TernaryValue nVal = NodeUtil.getPureBooleanValue(n);
if (nVal != TernaryValue.UNKNOWN) {
boolean result = nVal.toBoolean(true);
int equivalentResult = result ? 1 : 0;
return maybeReplaceChildWithNumber(n, parent, equivalentResult);
}
// We can't do anything else currently.
return n;
}
}
/**
* Replaces a node with a number node if the new number node is not equivalent
* to the current node.
*
* Returns the replacement for n if it was replaced, otherwise returns n.
*/
private Node maybeReplaceChildWithNumber(Node n, Node parent, int num) {
Node newNode = IR.number(num);
if (!newNode.isEquivalentTo(n)) {
parent.replaceChild(n, newNode);
reportCodeChange();
return newNode;
}
return n;
}
private static final ImmutableSet<String> STANDARD_OBJECT_CONSTRUCTORS =
// String, Number, and Boolean functions return non-object types, whereas
// new String, new Number, and new Boolean return object types, so don't
// include them here.
ImmutableSet.of(
"Object",
"Array",
"RegExp",
"Error"
);
/**
* Fold "new Object()" to "Object()".
*/
private Node tryFoldStandardConstructors(Node n) {
Preconditions.checkState(n.isNew());
// If name normalization has been run then we know that
// new Object() does in fact refer to what we think it is
// and not some custom-defined Object().
if (isASTNormalized())
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>emoizedScopeCreator
*/
static class MemoizedScopeCleanupPass implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
public MemoizedScopeCleanupPass(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
ScopeCreator creator = compiler.getTypedScopeCreator();
if (creator instanceof MemoizedScopeCreator) {
MemoizedScopeCreator scopeCreator = (MemoizedScopeCreator) creator;
String newSrc = scriptRoot.getSourceFileName();
for (Var var : scopeCreator.getAllSymbols()) {
JSType type = var.getType();
if (type != null) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null
&& newSrc.equals(NodeUtil.getSourceName(fnType.getSource()))) {
fnType.setSource(null);
}
}
}
scopeCreator.removeScopesForScript(originalRoot.getSourceFileName());
}
}
@Override
public void process(Node externs, Node root) {
// MemoizedScopeCleanupPass should not do work during process.
}
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> @Override
void endFunction(boolean statementContext) {
super.endFunction(statementContext);
if (statementContext) {
startNewLine();
}
}
@Override
void beginCaseBody() {
super.beginCaseBody();
indent++;
endLine();
}
@Override
void endCaseBody() {
super.endCaseBody();
indent--;
endStatement();
}
@Override
void appendOp(String op, boolean binOp) {
if (binOp) {
if (getLastChar() != ' ' && op.charAt(0) != ',') {
append(" ");
}
append(op);
append(" ");
} else {
append(op);
}
}
/**
* If the body of a for loop or the then clause of an if statement has
* a single statement, should it be wrapped in a block?
* {@inheritDoc}
*/
@Override
boolean shouldPreserveExtraBlocks() {
// When pretty-printing, always place the statement in its own block
// so it is printed on a separate line. This allows breakpoints to be
// placed on the statement.
return true;
}
/**
* @return The TRY node for the specified CATCH node.
*/
private Node getTryForCatch(Node n) {
return n.getParent().getParent();
}
/**
* @return Whether the a line break should be added after the specified
* BLOCK.
*/
@Override
boolean breakAfterBlockFor(Node n, boolean isStatementContext) {
Preconditions.checkState(n.isBlock());
Node parent = n.getParent();
if (parent != null) {
int type = parent.getType();
switch (type) {
case Token.DO:
// Don't break before 'while' in DO-WHILE statements.
return false;
case Token.FUNCTION:
// FUNCTIONs are handled separately, don't break here.
return false;
case Token.TRY:
// Don't break before catch
return n != parent.getFirstChild();
case Token.CATCH:
// Don't break before finally
return !NodeUtil.hasFinally(getTryForCatch(parent));
case Token.IF:
// Don't break before else
return n == parent.getLastChild();
}
}
return true;
}
@Override
void endFile() {
maybeEndStatement();
}
}
static class CompactCodePrinter
extends MappedCodePrinter {
// The CompactCodePrinter tries to emit just enough newlines to stop there
// being lines longer than the threshold. Since the output is going to be
// gzipped, it makes sense to try to make the newlines appear in similar
// contexts so that gzip can encode them for 'free'.
//
// This version tries to break the lines at 'preferred' places, which are
// between the top-level forms. This works because top-level forms tend to
// be more uniform than arbitrary legal contexts. Better compression would
// probably require explicit modeling of the gzip algorithm.
private final boolean lineBreak;
private final boolean preferLineBreakAtEndOfFile;
private int lineStartPosition = 0;
private int preferredBreakPosition = 0;
private int prevCutPosition = 0;
private int prevLineStartPosition =
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> people like keeping these semicolons around,
// so we'll allow it.
if (n.isEmpty() ||
n.isComma()) {
return;
}
if (parent == null) {
return;
}
// Do not try to remove a block or an expr result. We already handle
// these cases when we visit the child, and the peephole passes will
// fix up the tree in more clever ways when these are removed.
if (n.isExprResult() || n.isBlock()) {
return;
}
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
return;
}
boolean isResultUsed = NodeUtil.isExpressionResultUsed(n);
boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType());
if (!isResultUsed &&
(isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) {
String msg = "This code lacks side-effects. Is there a bug?";
if (n.isString()) {
msg = "Is there a missing '+' on the previous line?";
} else if (isSimpleOp) {
msg = "The result of the '" + Token.name(n.getType()).toLowerCase() +
"' operator is not being used.";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
// TODO(johnlenz): determine if it is necessary to
// try to protect side-effect free statements as well.
if (!NodeUtil.isStatement(n)) {
problemNodes.add(n);
}
}
}
/**
* Protect side-effect free nodes by making them parameters
* to a extern function call. This call will be removed
* after all the optimizations passes have run.
*/
private void protectSideEffects() {
if (!problemNodes.isEmpty()) {
addExtern();
for (Node n : problemNodes) {
Node name = IR.name(PROTECTOR_FN).srcref(n);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node replacement = IR.call(name).srcref(n);
replacement.putBooleanProp(Node.FREE_CALL, true);
n.getParent().replaceChild(n, replacement);
replacement.addChildToBack(n);
}
compiler.reportCodeChange();
}
}
private void addExtern() {
Node name = IR.name(PROTECTOR_FN);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node var = IR.var(name);
// Add "@noalias" so we can strip the method when AliasExternals is enabled.
JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
builder.recordNoAlias();
var.setJSDocInfo(builder.build(var));
CompilerInput input = compiler.getSynthesizedExternsInput();
input.getAstRoot(compiler).addChildrenToBack(var);
compiler.reportCodeChange();
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Prepare the AST before we do any checks or optimizations on it.
*
* This pass must run. It should bring the AST into a consistent state,
* and add annotations where necessary. It should not make any transformations
* on the tree that would lose source information, since we need that source
* information for checks.
*
* @author johnlenz@google.com (John Lenz)
*/
class PrepareAst implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean checkOnly;
PrepareAst(AbstractCompiler compiler) {
this(compiler, false);
}
PrepareAst(AbstractCompiler compiler, boolean checkOnly) {
this.compiler = compiler;
this.checkOnly = checkOnly;
}
private void reportChange() {
if (checkOnly) {
Preconditions.checkState(false, "normalizeNodeType constraints violated");
}
}
@Override
public void process(Node externs, Node root) {
if (checkOnly) {
normalizeNodeTypes(root);
} else {
// Don't perform "PrepareAnnotations" when doing checks as
// they currently aren't valid during sanity checks. In particular,
// they DIRECT_EVAL shouldn't be applied after inlining has been
// performed.
if (externs != null) {
NodeTraversal.traverse(
compiler, externs, new PrepareAnnotations(compiler));
}
if (root != null) {
NodeTraversal.traverse(
compiler, root, new PrepareAnnotations(compiler));
}
}
}
/**
* Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.is
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>ControlStructure(n)
&& !n.isLabel()
&& !n.isSwitch()) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
!c.isBlock()) {
Node newBlock = IR.block().srcref(n);
n.replaceChild(c, newBlock);
if (!c.isEmpty()) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
implements NodeTraversal.Callback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isObjectLit()) {
normalizeObjectLiteralAnnotations(n);
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateFunctions(n, parent);
annotateDispatchers(n, parent);
break;
}
}
private void normalizeObjectLiteralAnnotations(Node objlit) {
Preconditions.checkState(objlit.isObjectLit());
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.isCall());
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval()".
if (first.isName() &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.isFunction());
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.isAssign()) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> /**
* In the AST that Rhino gives us, it needs to make a distinction
* between JsDoc on the object literal node and JsDoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
private void normalizeObjectLiteralKeyAnnotations(
Node objlit, Node key, Node value) {
Preconditions.checkState(objlit.isObjectLit());
if (key.getJSDocInfo() != null &&
value.isFunction()) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
/**
* Annotate optional and var_arg function parameters.
*/
private void annotateFunctions(Node n, Node parent) {
JSDocInfo fnInfo = NodeUtil.getFunctionJSDocInfo(n);
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter(arg) ||
typeExpr != null && typeExpr.isVarArgs()) {
arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true);
}
}
}
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> should point to.
* @param superObject The expected super instance type.
* @param subObject The sub instance type.
*/
void expectSuperType(NodeTraversal t, Node n, ObjectType superObject,
ObjectType subObject) {
FunctionType subCtor = subObject.getConstructor();
ObjectType declaredSuper =
subObject.getImplicitPrototype().getImplicitPrototype();
if (!declaredSuper.equals(superObject)) {
if (declaredSuper.equals(getNativeType(OBJECT_TYPE))) {
registerMismatch(superObject, declaredSuper, report(
t.makeError(n, MISSING_EXTENDS_TAG_WARNING, subObject.toString())));
} else {
mismatch(t.getSourceName(), n,
"mismatch in declaration of superclass type",
superObject, declaredSuper);
}
// Correct the super type.
if (!subCtor.hasCachedValues()) {
subCtor.setPrototypeBasedOn(superObject);
}
}
}
/**
* Expect that the first type can be cast to the second type. The first type
* should be either a subtype or supertype of the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) {
castType = castType.restrictByNotNullOrUndefined();
type = type.restrictByNotNullOrUndefined();
if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) {
registerMismatch(type, castType, report(t.makeError(n, INVALID_CAST,
castType.toString(), type.toString())));
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
* @return The variable we end up with. Most of the time, this will just
* be {@code var}, but in some rare cases we will need to declare
* a new var with new source info.
*/
Var expectUndeclaredVariable(String sourceName, CompilerInput input,
Node n, Node parent, Var var, String variableName, JSType newType) {
Var newVar = var;
boolean allowDupe = false;
if (n.isGetProp() ||
NodeUtil.isObjectLitKey(n, parent)) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.Node;
/**
* An abstract class whose implementations run peephole optimizations:
* optimizations that look at a small section of code and either remove
* that code (if it is not needed) or replaces it with smaller code.
*
*/
abstract class AbstractPeepholeOptimization {
private AbstractCompiler compiler;
/**
* Given a node to optimize and a traversal, optimize the node. Subclasses
* should override to provide their own peephole optimization.
*
* @param subtree The subtree that will be optimized.
* @return The new version of the subtree (or null if the subtree or one of
* its parents was removed from the AST). If the subtree has not changed,
* this method must return {@code subtree}.
*/
abstract Node optimizeSubtree(Node subtree);
/**
* Helper method for reporting an error to the compiler when applying a
* peephole optimization.
*
* @param diagnostic The error type
* @param n The node for which the error should be reported
*/
protected void error(DiagnosticType diagnostic, Node n) {
JSError error =
JSError.make(NodeUtil.getSourceName(n), n, diagnostic, n.toString());
compiler.report(error);
}
/**
* Helper method for telling the compiler that something has changed.
* Subclasses must call these if they have changed the AST.
*/
protected void reportCodeChange() {
Preconditions.checkNotNull(compiler);
compiler.reportCodeChange();
}
/**
* Are the nodes equal for the purpose of inlining?
* If type aware optimizations are on, type equality is checked.
*/
protected boolean areNodesEqualForInlining(Node n1, Node n2) {
/* Our implementation delegates to the compiler. We provide this
* method because we don't want to expose Compiler to PeepholeOptimizations.
*/
Preconditions.checkNotNull(compiler);
return compiler.areNodesEqualForInlining(n1, n2);
}
/**
* Is the current AST normalized? (e.g. has the Normalize pass been run
* and has the Denormalize pass not yet been run?)
*/
protected boolean isASTNormalized() {
Preconditions.checkNotNull(compiler);
return compiler.getLifeCycleStage().isNormalized();
}
/**
* Informs the optimization that a traversal will begin.
*/
void beginTraversal(AbstractCompiler compiler) {
this.
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>compiler = compiler;
}
/**
* Informs the optimization that a traversal has completed.
*/
void endTraversal(AbstractCompiler compiler) {
this.compiler = null;
}
// NodeUtil's mayEffectMutableState and mayHaveSideEffects need access to the
// compiler object, route them through here to give them access.
/**
* @return Whether the node may create new mutable state, or change existing
* state.
*/
boolean mayEffectMutableState(Node n) {
return NodeUtil.mayEffectMutableState(n, compiler);
}
/**
* @return Whether the node may have side effects when executed.
*/
boolean mayHaveSideEffects(Node n) {
return NodeUtil.mayHaveSideEffects(n, compiler);
}
/**
* @return Whether the source code version is ECMAScript 5 or later.
* Workarounds for quirks in browsers that do not support ES5 can be
* ignored when this is true.
*/
boolean isEcmaScript5OrGreater() {
return compiler != null
&& compiler.acceptEcmaScript5();
}
/**
* @return the current coding convention.
*/
CodingConvention getCodingConvention() {
// Note: this assumes a thread safe coding convention object.
return compiler.getCodingConvention();
}
/**
* Check if the specified node is null or is still in the AST.
*/
@VisibleForTesting
static Node validateResult(Node n) {
done: {
if (n != null && !n.isScript()
&& (!n.isBlock() || !n.isSyntheticBlock())) {
for (Node parent : n.getAncestors()) {
if (parent.isScript()) {
break done;
}
}
Preconditions.checkState(false);
}
}
return n;
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING_KEY)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends != null &&
maybeLends.getLendsName() != null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
}
if (parent != null && parent.isAssign()) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.isGetProp() &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.isGetProp() &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isThis() && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ... x = function
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>.isName());
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.isCall()) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, especially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of value.
* @return Whether we should remove this assignment from the parse tree.
*/
private boolean processDefineAssignment(NodeTraversal t,
String name, Node value, Node valueParent) {
if (value == null || !NodeUtil.isValidDefineValue(value,
allDefines.keySet())) {
compiler.report(
t.makeError(value, INVALID_DEFINE_INIT_ERROR, name));
} else if (!isAssignAllowed()) {
compiler.report(
t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name));
} else {
DefineInfo info = allDefines.get(name);
if (info == null) {
// First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
assignableDefines.put(name, info);
} else if (info.recordAssignment(value)) {
// The define was already initialized, but this is a safe
// re-assignment.
return true;
} else {
// The define
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
* @return whether the children of this node should be visited
*/
boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent);
/**
* <p>Visits a node in post order (after its children have been visited).
* A node is visited only if all its parents should be traversed
* ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
*/
void visit(NodeTraversal t, Node n, Node parent);
}
/**
* Callback that also knows about scope changes
*/
public interface ScopedCallback extends Callback {
/**
* Called immediately after entering a new scope. The new scope can
* be accessed through t.getScope()
*/
void enterScope(NodeTraversal t);
/**
* Called immediately before exiting a scope. The ending scope can
* be accessed through t.getScope()
*/
void exitScope(NodeTraversal t);
}
/**
* Abstract callback to visit all nodes in post order.
*/
public abstract static class AbstractPostOrderCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
}
/**
* Abstract scoped callback to visit all nodes in post order.
*/
public abstract static class AbstractScopedCallback
implements ScopedCallback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {}
@Override
public void exitScope(NodeTraversal t) {}
}
/**
* Abstract callback to visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || !parent.isFunction() ||
n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't
* traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> * @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.inputId = null;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (inputId != null) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
inputId = NodeUtil.getInputId(root);
sourceName = "";
curNode = root;
pushScope(root);
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
inputId = NodeUtil.getInputId(scopeRoot);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
if (n == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.isGlobal());
inputId = null;
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.isFunction()) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
}
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild().getNext();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
/**
* Gets the compiler.
*/
public Compiler getCompiler() {
// TODO(nicksantos): Remove this type cast. This is just temporary
// while refactoring.
return (Compiler) compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur !=
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> null) {
int line = cur.getLineno();
if (line >=0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
*/
public CompilerInput getInput() {
return compiler.getInput(inputId);
}
/**
* Gets the current input module.
*/
public JSModule getModule() {
CompilerInput input = getInput();
return input == null ? null : input.getModule();
}
/** Returns the node currently being traversed. */
public Node getCurrentNode() {
return curNode;
}
/**
* Traverses a node recursively.
*/
public static void traverse(
AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverse(root);
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
public static void traverseRoots(
AbstractCompiler compiler, Callback cb, Node ... roots) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
@SuppressWarnings("fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
inputId = n.getInputId();
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.isFunction());
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverse
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>Break = false;
preferLineBreakAtEndOfFile = false;
reportPath = null;
tracer = TracerMode.OFF;
colorizeErrorOutput = false;
errorFormat = ErrorFormat.SINGLELINE;
debugFunctionSideEffectsPath = null;
externExports = false;
nameReferenceReportPath = null;
nameReferenceGraphPath = null;
// Debugging
aliasHandler = NULL_ALIAS_TRANSFORMATION_HANDLER;
errorHandler = null;
}
/**
* @return Whether to attempt to remove unused class properties
*/
public boolean isRemoveUnusedClassProperties() {
return removeUnusedClassProperties;
}
/**
* @param removeUnusedClassProperties Whether to attempt to remove
* unused class properties
*/
public void setRemoveUnusedClassProperties(boolean removeUnusedClassProperties) {
this.removeUnusedClassProperties = removeUnusedClassProperties;
}
/**
* Returns the map of define replacements.
*/
public Map<String, Node> getDefineReplacements() {
return getReplacementsHelper(defineReplacements);
}
/**
* Returns the map of tweak replacements.
*/
public Map<String, Node> getTweakReplacements() {
return getReplacementsHelper(tweakReplacements);
}
/**
* Creates a map of String->Node from a map of String->Number/String/Boolean.
*/
private static Map<String, Node> getReplacementsHelper(
Map<String, Object> source) {
Map<String, Node> map = Maps.newHashMap();
for (Map.Entry<String, Object> entry : source.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (value instanceof Boolean) {
map.put(name, NodeUtil.booleanNode(((Boolean) value).booleanValue()));
} else if (value instanceof Integer) {
map.put(name, IR.number(((Integer) value).intValue()));
} else if (value instanceof Double) {
map.put(name, IR.number(((Double) value).doubleValue()));
} else {
Preconditions.checkState(value instanceof String);
map.put(name, IR.string((String) value));
}
}
return map;
}
/**
* Sets the value of the {@code @define} variable in JS
* to a boolean literal.
*/
public void setDefineToBooleanLiteral(String defineName, boolean value) {
defineReplacements.put(defineName, new Boolean(value));
}
/**
* Sets the value of the {@code @define} variable in JS to a
* String literal.
*/
public void setDefineToStringLiteral(String defineName, String value) {
defineReplacements.put(defineName, value);
}
/**
* Sets the value of the {@code @define} variable in JS to a
* number literal.
*/
public void setDefineToNumberLiteral(String defineName, int value) {
defineReplacements.put(defineName, new Integer(value));
}
/**
* Sets the value of the {@code @define} variable in JS to a
* number literal.
*/
public void setDefineToDoubleLiteral(String defineName, double value) {
defineReplacements.put(defineName, new Double(value));
}
/**
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> bush formation, module 2 depends
* on module 1, and all other modules depend on module 2.
*/
static JSModule[] createModuleBush(String ... inputs) {
Preconditions.checkState(inputs.length > 2);
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[i == 1 ? 0 : 1]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs, such that modules
* form a tree formation. In a tree formation, module N depends on
* module `floor(N/2)`, So the modules form a balanced binary tree.
*/
static JSModule[] createModuleTree(String ... inputs) {
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[(i - 1) / 2]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs. Does not generate any
* dependencies between the modules.
*/
static JSModule[] createModules(String... inputs) {
JSModule[] modules = new JSModule[inputs.length];
for (int i = 0; i < inputs.length; i++) {
JSModule module = modules[i] = new JSModule("m" + i);
module.add(SourceFile.fromCode("i" + i, inputs[i]));
}
return modules;
}
private static class BlackHoleErrorManager extends BasicErrorManager {
private BlackHoleErrorManager(Compiler compiler) {
compiler.setErrorManager(this);
}
@Override
public void println(CheckLevel level, JSError error) {}
@Override
public void printSummary() {}
}
Compiler createCompiler() {
Compiler compiler = new Compiler();
return compiler;
}
protected void setExpectedSymbolTableError(DiagnosticType type) {
this.expectedSymbolTableError = type;
}
/** Finds the first matching qualified name node in post-traversal order. */
protected final Node findQualifiedNameNode(final String name, Node root) {
final List<Node> matches = Lists.newArrayList();
NodeUtil.visitPostOrder(root,
new NodeUtil.Visitor() {
@Override public void visit(Node n) {
if (name.equals(n.getQualifiedName())) {
matches.add(n);
}
}
},
Predicates.<Node>alwaysTrue());
return matches.get(0);
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
import com.google.debugging.sourcemap.FilePosition;
import com.google.debugging.sourcemap.SourceMapFormat;
import com.google.debugging.sourcemap.SourceMapGenerator;
import com.google.debugging.sourcemap.SourceMapGeneratorFactory;
import com.google.debugging.sourcemap.SourceMapGeneratorV1;
import com.google.debugging.sourcemap.SourceMapGeneratorV2;
import com.google.javascript.rhino.Node;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Collects information mapping the generated (compiled) source back to
* its original source for debugging purposes.
*
* @see CodeConsumer
* @see CodeGenerator
* @see CodePrinter
*
*/
public class SourceMap {
public static enum Format {
V1 {
@Override SourceMap getInstance() {
return new SourceMap(
SourceMapGeneratorFactory.getInstance(SourceMapFormat.V1));
}
},
DEFAULT {
@Override SourceMap getInstance() {
return new SourceMap(
SourceMapGeneratorFactory.getInstance(SourceMapFormat.DEFAULT));
}
},
V2 {
@Override SourceMap getInstance() {
return new SourceMap(
SourceMapGeneratorFactory.getInstance(SourceMapFormat.V2));
}
},
V3 {
@Override SourceMap getInstance() {
return new SourceMap(
SourceMapGeneratorFactory.getInstance(SourceMapFormat.V3));
}
};
abstract SourceMap getInstance();
}
/**
* Source maps can be very large different levels of detail can be specified.
*/
public static enum DetailLevel implements Predicate<Node> {
// ALL is best when the fullest details are needed for debugging or for
// code-origin analysis.
ALL {
@Override public boolean apply(Node node) {
return true;
}
},
// SYMBOLS is intended to be used for stack trace deobfuscation when full
// detail is not needed.
SYMBOLS {
@Override public boolean apply(Node node) {
return node.isCall()
|| node.isNew()
|| node.isFunction()
|| node.isName()
|| NodeUtil.isGet(node)
|| NodeUtil.isObjectLitKey(node, node.getParent())
|| (node.isString() && NodeUtil.isGet(node.getParent()));
}
};
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> final boolean sanityCheck;
// Whether extern checks emit error.
private final boolean strictExternCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.strictExternCheck = compiler.getErrorLevel(
JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
// Don't run externs-checking in sanity check mode. Normalization will
// remove duplicate VAR declarations, which will make
// externs look like they have assigns.
if (!sanityCheck) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
}
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
for (String varName : varsToDeclareInExterns) {
createSynthesizedExternVar(varName);
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
Preconditions.checkState(scriptRoot.isScript());
NodeTraversal t = new NodeTraversal(compiler, this);
// Note we use the global scope to prevent wrong "undefined-var errors" on
// variables that are defined in other JS files.
t.traverseWithScope(scriptRoot,
SyntacticScopeCreator.generateUntypedTopScope(compiler));
// TODO(bashir) Check if we need to createSynthesizedExternVar like process.
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isName()) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(parent.isFunction());
Preconditions.checkState(NodeUtil.isFunctionExpression(parent));
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.isVar() ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
n.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(varName, n,
null, getSynthesizedExtern
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> nameNode,
BasicBlock basicBlock, Scope scope, InputId inputId) {
this.nameNode = nameNode;
this.basicBlock = basicBlock;
this.scope = scope;
this.inputId = inputId;
this.sourceFile = nameNode.getStaticSourceFile();
}
/**
* Makes a copy of the current reference using a new Scope instance.
*/
Reference cloneWithNewScope(Scope newScope) {
return new Reference(nameNode, basicBlock, newScope, inputId);
}
@Override
public Var getSymbol() {
return scope.getVar(nameNode.getString());
}
@Override
public Node getNode() {
return nameNode;
}
public InputId getInputId() {
return inputId;
}
@Override
public StaticSourceFile getSourceFile() {
return sourceFile;
}
boolean isDeclaration() {
Node parent = getParent();
Node grandparent = parent.getParent();
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.isParamList() &&
grandparent.isFunction();
}
boolean isVarDeclaration() {
return getParent().isVar();
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(getParent());
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
!getParent().isVar() ||
nameNode.getFirstChild() != null;
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
Node parent = getParent();
return (parent.isFunction())
? parent : NodeUtil.getAssignedValue(nameNode);
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return getNode().getParent();
}
Node getGrandparent() {
Node parent = getParent();
return parent == null ? null : parent.getParent();
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.isVar()) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
Node parent = getParent();
return parent.isAssign()
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
Node parent = getParent();
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.isFunction();
if (root.getParent() != null) {
int pType = root.getParent().getType();
this.isLoop = pType == Token.DO ||
pType == Token.WHILE ||
pType == Token.FOR;
} else {
this.isLoop = false;
}
}
BasicBlock getParent() {
return parent;
}
/**
* Determines whether this block is equivalent to the very first block that
* is created when reference collection traversal enters global scope. Note
* that when traversing a single script in a hot-swap fashion a new instance
* of {@code BasicBlock} is created.
*
* @return true if this is global scope block.
*/
boolean isGlobalScopeBlock() {
return getParent() == null;
}
/**
* Determines whether this block is guaranteed to begin executing before
* the given block does.
*/
boolean provablyExecutesBefore(BasicBlock thatBlock) {
// If thatBlock is a descendant of this block, and there are no hoisted
// blocks between them, then this block must start before thatBlock.
BasicBlock currentBlock;
for (currentBlock = thatBlock;
currentBlock != null && currentBlock != this;
currentBlock = currentBlock.getParent()) {
if (currentBlock.isHoisted) {
return false;
}
}
if (currentBlock == this) {
return true;
}
if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) {
return true;
}
return false;
}
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> Token.SETTER_DEF:
// Object literal keys are handled with OBJECTLIT
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.isAssign() &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !parent.isExprResult();
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
if (!childType.matchesInt32Context()) {
report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
// We do not want to warn about explicit comparisons to VOID. People
// often do this if they think their type annotations screwed up.
//
// We do want to warn about cases where people compare things like
// (Array|null) == (Function|null)
// because it probably means they screwed up.
//
// This heuristic here is not perfect, but should catch cases we
// care about without too many false negatives.
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result = TernaryValue.UNKNOWN;
if (n.getType() == Token.
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>Of(
t, assign, getJSType(rvalue),
expectedType, object, property);
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, property, info, expectedType);
return;
}
}
}
// If we couldn't get the property type with normal object property
// lookups, then check inheritance anyway with the unknown type.
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, property, info, getNativeType(UNKNOWN_TYPE));
}
// Check qualified name sets to 'object' and 'object.property'.
// This can sometimes handle cases when the type of 'object' is not known.
// e.g.,
// var obj = createUnknownType();
// /** @type {number} */ obj.foo = true;
JSType leftType = getJSType(lvalue);
if (lvalue.isQualifiedName()) {
// variable with inferred type case
JSType rvalueType = getJSType(assign.getLastChild());
Var var = t.getScope().getVar(lvalue.getQualifiedName());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
if (NodeUtil.getRootOfQualifiedName(lvalue).isThis() &&
t.getScope() != var.getScope()) {
// Don't look at "this.foo" variables from other scopes.
return;
}
if (var.getType() != null) {
leftType = var.getType();
}
}
}
// Fall through case for arbitrary LHS and arbitrary RHS.
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
private void checkPropertyInheritanceOnGetpropAssign(
NodeTraversal t, Node assign, Node object, String property,
JSDocInfo info, JSType propertyType) {
// Inheritance checks for prototype properties.
//
// TODO(nicksantos): This isn't the right place to do this check. We
// really want to do this when we're looking at the constructor.
// We'd find all its properties and make sure they followed inheritance
// rules, like we currently do for @implements to make sure
// all the methods are implemented.
//
// As-is, this misses many other ways to override a property.
//
// object.prototype.property = ...;
if (object.isGetProp()) {
Node object2 = object.getFirstChild();
String property2 = NodeUtil.getStringValue(object.getLastChild());
if ("prototype".equals(property2)) {
JSType jsType = getJSType(object2);
if (jsType.isFunctionType()) {
FunctionType functionType = jsType.toMaybeFunctionType();
if (functionType.isConstructor() || functionType.isInterface()) {
checkDeclaredPropertyInheritance(
t, assign, functionType, property, info, propertyType);
}
}
}
}
}
/**
* Visits an object literal field definition <code>key : value</code>.
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>
*
* If the <code>lvalue</code> is a prototype modification, we change the
* schema of the object type it is referring to.
*
* @param t the traversal
* @param key the assign node
*/
private void visitObjLitKey(NodeTraversal t, Node key, Node objlit) {
// Do not validate object lit value types in externs. We don't really care,
// and it makes it easier to generate externs.
if (objlit.isFromExterns()) {
ensureTyped(t, key);
return;
}
// TODO(johnlenz): Validate get and set function declarations are valid
// as is the functions can have "extraneous" bits.
// For getter and setter property definitions the
// r-value type != the property type.
Node rvalue = key.getFirstChild();
JSType rightType = NodeUtil.getObjectLitKeyTypeFromValueType(
key, getJSType(rvalue));
if (rightType == null) {
rightType = getNativeType(UNKNOWN_TYPE);
}
Node owner = objlit;
// Validate value is assignable to the key type.
JSType keyType = getJSType(key);
JSType allowedValueType = keyType;
if (allowedValueType.isEnumElementType()) {
allowedValueType =
allowedValueType.toMaybeEnumElementType().getPrimitiveType();
}
boolean valid = validator.expectCanAssignToPropertyOf(t, key,
rightType, allowedValueType,
owner, NodeUtil.getObjectLitKeyName(key));
if (valid) {
ensureTyped(t, key, rightType);
} else {
ensureTyped(t, key);
}
// Validate that the key type is assignable to the object property type.
// This is necessary as the objlit may have been cast to a non-literal
// object type.
// TODO(johnlenz): consider introducing a CAST node to the AST (or
// perhaps a parentheses node).
JSType objlitType = getJSType(objlit);
ObjectType type = ObjectType.cast(
objlitType.restrictByNotNullOrUndefined());
if (type != null) {
String property = NodeUtil.getObjectLitKeyName(key);
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
validator.expectCanAssignToPropertyOf(
t, key, keyType,
type.getPropertyType(property), owner, property);
}
return;
}
}
/**
* Returns true if any type in the chain has an implicitCast annotation for
* the given property.
*/
private boolean propertyIsImplicitCast(ObjectType type, String prop) {
for (; type != null; type = type.getImplicitPrototype()) {
JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isImplicitCast()) {
return true;
}
}
return false;
}
/**
* Given a constructor type and a property name, check that the property has
* the JSDoc annotation @override iff the property is declared on a
* superclass. Several checks regarding inheritance correctness are also
* performed.
*/
private void checkDeclared
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>toString(), propertyType.toString()));
}
} else if (superInterfaceHasDeclaredProperty) {
// there is an super interface property
for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) {
if (interfaceType.hasProperty(propertyName)) {
JSType superPropertyType =
interfaceType.getPropertyType(propertyName);
if (!propertyType.canAssignTo(superPropertyType)) {
topInstanceType = interfaceType.getConstructor().
getTopMostDefiningType(propertyName);
compiler.report(
t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
propertyName, topInstanceType.toString(),
superPropertyType.toString(),
propertyType.toString()));
}
}
}
} else if (!foundInterfaceProperty
&& !superClassHasProperty
&& !superInterfaceHasProperty) {
// there is no superclass nor interface implementation
compiler.report(
t.makeError(n, UNKNOWN_OVERRIDE,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Given a constructor or an interface type, find out whether the unknown
* type is a supertype of the current type.
*/
private static boolean hasUnknownOrEmptySupertype(FunctionType ctor) {
Preconditions.checkArgument(ctor.isConstructor() || ctor.isInterface());
Preconditions.checkArgument(!ctor.isUnknownType());
// The type system should notice inheritance cycles on its own
// and break the cycle.
while (true) {
ObjectType maybeSuperInstanceType =
ctor.getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return false;
}
if (maybeSuperInstanceType.isUnknownType() ||
maybeSuperInstanceType.isEmptyType()) {
return true;
}
ctor = maybeSuperInstanceType.getConstructor();
if (ctor == null) {
return false;
}
Preconditions.checkState(ctor.isConstructor() || ctor.isInterface());
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
// Only 2 values are allowed for methods:
// goog.abstractMethod
// function () {};
// or for properties, no assignment such as:
// InterfaceFoo.prototype.foobar;
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isFunctionType()) {
// This is bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().isFunction()
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits a NAME node.
*
* @
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> would check if the
* property *can be undefined*.
*/
private void checkPropertyAccess(JSType childType, String propName,
NodeTraversal t, Node n) {
// If the property type is unknown, check the object type to see if it
// can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for
// properties where we've checked that it exists, or for properties on
// objects that aren't in this binary).
JSType propType = getJSType(n);
if (propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
childType = childType.autobox();
ObjectType objectType = ObjectType.cast(childType);
if (objectType != null) {
// We special-case object types so that checks on enums can be
// much stricter, and so that we can use hasProperty (which is much
// faster in most cases).
if (!objectType.hasProperty(propName) ||
objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
report(t, n, INEXISTENT_ENUM_ELEMENT, propName);
} else {
checkPropertyAccessHelper(objectType, propName, t, n);
}
}
} else {
checkPropertyAccessHelper(childType, propName, t, n);
}
}
}
private void checkPropertyAccessHelper(JSType objectType, String propName,
NodeTraversal t, Node n) {
if (!objectType.isEmptyType() &&
reportMissingProperties && !isPropertyTest(n)) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
report(t, n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirstChild(), true));
}
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().isOr() &&
parent.getParent().getFirstChild() == parent;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> that this must be a var_args function.
if (parameters.hasNext()) {
parameter = parameters.next();
}
argument = arguments.next();
ordinal++;
validator.expectArgumentMatchesParameter(t, argument,
getJSType(argument), getJSType(parameter), call, ordinal);
}
int numArgs = call.getChildCount() - 1;
int minArgs = functionType.getMinArguments();
int maxArgs = functionType.getMaxArguments();
if (minArgs > numArgs || maxArgs < numArgs) {
report(t, call, WRONG_ARGUMENT_COUNT,
validator.getReadableJSTypeName(call.getFirstChild(), false),
String.valueOf(numArgs), String.valueOf(minArgs),
maxArgs != Integer.MAX_VALUE ?
" and no more than " + maxArgs + " argument(s)" : "");
}
}
/**
* Visits a RETURN node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitReturn(NodeTraversal t, Node n) {
Node function = t.getEnclosingFunction();
// This is a misplaced return, but the real JS will fail to compile,
// so let it go.
if (function == null) {
return;
}
JSType jsType = getJSType(function);
if (jsType.isFunctionType()) {
FunctionType functionType = jsType.toMaybeFunctionType();
JSType returnType = functionType.getReturnType();
// if no return type is specified, undefined must be returned
// (it's a void function)
if (returnType == null) {
returnType = getNativeType(VOID_TYPE);
}
// fetching the returned value's type
Node valueNode = n.getFirstChild();
JSType actualReturnType;
if (valueNode == null) {
actualReturnType = getNativeType(VOID_TYPE);
valueNode = n;
} else {
actualReturnType = getJSType(valueNode);
}
// verifying
validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
if (!leftType.matchesInt32Context()) {
report(t, left, BIT_OPERATION,
Node
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>Util.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
report(t, right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
break;
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right, rightType, "right operand");
break;
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
validator.expectBitwiseable(t, left, leftType,
"bad left operand to bitwise operator");
validator.expectBitwiseable(t, right, rightType,
"bad right operand to bitwise operator");
break;
case Token.ASSIGN_ADD:
case Token.ADD:
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(op));
}
ensureTyped(t, n);
}
/**
* <p>Checks enum aliases.
*
* <p>We verify that the enum element type of the enum used
* for initialization is a subtype of the enum element type of
* the enum the value is being copied in.</p>
*
* <p>Example:</p>
* <pre>var myEnum = myOtherEnum;</pre>
*
* <p>Enum aliases are irregular, so we need special code for this :(</p>
*
* @param value the value used for initialization of the enum
*/
private void checkEnumAlias(
NodeTraversal t, JSDocInfo declInfo, Node value) {
if (declInfo == null || !declInfo.hasEnumParameterType()) {
return;
}
JSType valueType = getJSType(value);
if (!valueType.isEnumType()) {
return;
}
EnumType valueEnumType = valueType.toMaybeEnumType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
declInfo.getEnumParameterType().evaluate(t.getScope(), typeRegistry),
"incompatible enum element types");
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
// TODO(nicks
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.regex.RegExpTree;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable, and checks that regular expressions
* are syntactically valid.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning(
"JSC_MALFORMED_REGEXP",
"Malformed Regular Expression: {0}");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
// Check the syntax of regular expression patterns.
} else if (n.isRegExp()) {
String pattern = n.getFirstChild().getString();
String flags = n.getChildCount() == 2
? n.getLastChild().getString() : "";
try {
RegExpTree.parseRegExp(pattern, flags);
} catch (IllegalArgumentException ex) {
t.report(n, MALFORMED_REGEXP, ex.getMessage());
}
}
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> that checks variables for redeclaration or early references
* just after they go out of scope.
*/
private class ReferenceCheckingBehavior implements Behavior {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {
// TODO(bashir) In hot-swap version this means that for global scope we
// only go through all global variables accessed in the modified file not
// all global variables. This should be fixed.
// Check all vars after finishing a scope
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
checkVar(t, v, referenceMap.getReferences(v).references);
}
}
/**
* If the variable is declared more than once in a basic block, generate a
* warning. Also check if a variable is used in a given scope before it is
* declared, which suggest a likely error. Relies on the fact that
* references is in parse-tree order.
*/
private void checkVar(NodeTraversal t, Var v, List<Reference> references) {
blocksWithDeclarations.clear();
boolean isDeclaredInScope = false;
boolean isUnhoistedNamedFunction = false;
Reference hoistedFn = null;
// Look for hoisted functions.
for (Reference reference : references) {
if (reference.isHoistedFunction()) {
blocksWithDeclarations.add(reference.getBasicBlock());
isDeclaredInScope = true;
hoistedFn = reference;
break;
} else if (NodeUtil.isFunctionDeclaration(
reference.getNode().getParent())) {
isUnhoistedNamedFunction = true;
}
}
for (Reference reference : references) {
if (reference == hoistedFn) {
continue;
}
BasicBlock basicBlock = reference.getBasicBlock();
boolean isDeclaration = reference.isDeclaration();
boolean allowDupe =
SyntacticScopeCreator.hasDuplicateDeclarationSuppression(
reference.getNode(), v);
if (isDeclaration && !allowDupe) {
// Look through all the declarations we've found so far, and
// check if any of them are before this block.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (declaredBlock.provablyExecutesBefore(basicBlock)) {
// TODO(johnlenz): Fix AST generating clients that so they would
// have property StaticSourceFile attached at each node. Or
// better yet, make sure the generated code never violates
// the requirement to pass aggressive var check!
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
REDECLARED_VARIABLE, v.name));
break;
}
}
}
if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) {
// Only allow an unhoisted named function to be used within the
// block it is declared.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (!declaredBlock.provablyExecutesBefore(basicBlock)) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
AMBIGUOUS_FUNCTION_DECL, v.name));
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>
break;
}
}
}
if (!isDeclaration && !isDeclaredInScope) {
// Don't check the order of refer in externs files.
if (!reference.getNode().isFromExterns()) {
// Special case to deal with var goog = goog || {}
Node grandparent = reference.getGrandparent();
if (grandparent.isName()
&& grandparent.getString() == v.name) {
continue;
}
// Only generate warnings if the scopes do not match in order
// to deal with possible forward declarations and recursion
if (reference.getScope() == v.scope) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
UNDECLARED_REFERENCE, v.name));
}
}
}
if (isDeclaration) {
blocksWithDeclarations.add(basicBlock);
isDeclaredInScope = true;
}
}
}
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> validateBlock(n.getLastChild());
}
private void validateFunctionExpression(Node n) {
validateNodeType(Token.FUNCTION, n);
validateChildCount(n, 3);
validateOptionalName(n.getFirstChild());
validateParameters(n.getChildAtIndex(1));
validateBlock(n.getLastChild());
}
private void validateParameters(Node n) {
validateNodeType(Token.PARAM_LIST, n);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateName(c);
}
}
private void validateCall(Node n) {
validateNodeType(Token.CALL, n);
validateMinimumChildCount(n, 1);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateExpression(c);
}
}
private void validateNew(Node n) {
validateNodeType(Token.NEW, n);
validateMinimumChildCount(n, 1);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateExpression(c);
}
}
private void validateVar(Node n) {
validateNodeType(Token.VAR, n);
this.validateMinimumChildCount(n, 1);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
// Don't use the validateName here as the NAME is allowed to have
// a child.
validateNodeType(Token.NAME, c);
validateNonEmptyString(c);
validateMaximumChildCount(c, 1);
if (c.hasChildren()) {
validateExpression(c.getFirstChild());
}
}
}
private void validateFor(Node n) {
validateNodeType(Token.FOR, n);
validateMinimumChildCount(n, 3);
validateMaximumChildCount(n, 4);
if (NodeUtil.isForIn(n)) {
// FOR-IN
validateChildCount(n, 3);
validateVarOrAssignmentTarget(n.getFirstChild());
validateExpression(n.getChildAtIndex(1));
} else {
// FOR
validateChildCount(n, 4);
validateVarOrOptionalExpression(n.getFirstChild());
validateOptionalExpression(n.getChildAtIndex(1));
validateOptionalExpression(n.getChildAtIndex(2));
}
validateBlock(n.getLastChild());
}
private void validateVarOrOptionalExpression(Node n) {
if (n.isVar()) {
validateVar(n);
} else {
validateOptionalExpression(n);
}
}
private void validateVarOrAssignmentTarget(Node n) {
if (n.isVar()) {
// Only one NAME can be declared for FOR-IN expressions.
this.validateChildCount(n, 1);
validateVar(n);
} else {
validateAssignmentTarget(n);
}
}
private void validateWith(Node n) {
validateNodeType(Token.WITH, n);
validateChildCount(n, 2);
validateExpression(n.getFirstChild());
validateBlock(n.getLastChild());
}
private void validateWhile(Node n) {
validateNodeType(Token.WHILE, n);
validateChildCount(n
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* <p>The syntactic scope creator scans the parse tree to create a Scope object
* containing all the variable declarations in that scope.</p>
*
* <p>This implementation is not thread-safe.</p>
*
*/
class SyntacticScopeCreator implements ScopeCreator {
private final AbstractCompiler compiler;
private Scope scope;
private InputId inputId;
private final RedeclarationHandler redeclarationHandler;
// The arguments variable is special, in that it's declared in every local
// scope, but not explicitly declared.
private static final String ARGUMENTS = "arguments";
public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR =
DiagnosticType.error(
"JSC_VAR_MULTIPLY_DECLARED_ERROR",
"Variable {0} first declared in {1}");
public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR =
DiagnosticType.error(
"JSC_VAR_ARGUMENTS_SHADOWED_ERROR",
"Shadowing \"arguments\" is not allowed");
/**
* Creates a ScopeCreator.
*/
SyntacticScopeCreator(AbstractCompiler compiler) {
this.compiler = compiler;
this.redeclarationHandler = new DefaultRedeclarationHandler();
}
SyntacticScopeCreator(
AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
@Override
public Scope createScope(Node n, Scope parent) {
inputId = null;
if (parent == null) {
scope = new Scope(n, compiler);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
inputId = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
if (n.isFunction()) {
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
// TODO(johnlenz): inputId maybe null if the FUNCTION node is detached
// from the AST.
// Is it meaningful to build a scope for detached FUNCTION node?
}
final Node fnNameNode = n.getFirstChild();
final Node args
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.isParamList());
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.isName());
declareVar(a);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().isName());
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
inputId = n.getInputId();
Preconditions.checkNotNull(inputId);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top-level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>(name);
Node origParent = origVar.getParentNode();
if (origParent.isCatch() &&
parent.isCatch()) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar);
if (!allowDupe) {
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
*/
private void declareVar(Node n) {
Preconditions.checkState(n.isName());
CompilerInput input = compiler.getInput(inputId);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
/**
* @param n The name node to check.
* @param origVar The associated Var.
* @return Whether duplicated declarations warnings should be suppressed
* for the given node.
*/
static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
Node origParent = origVar.getParentNode();
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
if (info != null && info.getSuppressions().contains("duplicate")) {
return true;
}
info = origVar.nameNode.getJSDocInfo();
if (info == null) {
info = origParent.getJSDocInfo();
}
return (info != null && info.getSuppressions().contains("duplicate"));
}
/**
* Generates an untyped global scope from the root of AST of compiler (which
* includes externs).
*
* @param compiler The compiler for which the scope is generated.
* @return The new untyped global scope generated as a result of this call.
*/
static Scope generateUntypedTopScope(AbstractCompiler compiler) {
return new SyntacticScopeCreator(compiler).createScope(compiler.getRoot(),
null);
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import java.nio.charset.Charset;
import java.util.Set;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*/
class TypedCodeGenerator extends CodeGenerator {
TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) {
super(consumer, outputCharset);
}
@Override
void add(Node n, Context context) {
Node parent = n.getParent();
if (parent != null
&& (parent.isBlock()
|| parent.isScript())) {
if (n.isFunction()) {
add(getFunctionAnnotation(n));
} else if (n.isExprResult()
&& n.getFirstChild().isAssign()) {
Node rhs = n.getFirstChild().getLastChild();
add(getTypeAnnotation(rhs));
} else if (n.isVar()
&& n.getFirstChild().getFirstChild() != null) {
add(getTypeAnnotation(n.getFirstChild().getFirstChild()));
}
}
super.add(n, context);
}
private String getTypeAnnotation(Node node) {
// Only add annotations for things with JSDoc, or function literals.
JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(node);
if (jsdoc == null && !node.isFunction()) {
return "";
}
JSType type = node.getJSType();
if (type == null) {
return "";
} else if (type.isFunctionType()) {
return getFunctionAnnotation(node);
} else if (type.isEnumType()) {
return "/** @enum {" +
type.toMaybeEnumType().getElementsType().toAnnotationString() +
"} */\n";
} else if (!type.isUnknownType()
&& !type.isEmptyType()
&& !type.isVoidType()
&& !type.isFunctionPrototypeType()) {
return "/** @type {" + node.getJSType().toAnnotationString() + "} */\n";
} else {
return "";
}
}
/**
* @param fnNode A node for a function for which to generate a type annotation
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>
*/
private String getFunctionAnnotation(Node fnNode) {
Preconditions.checkState(fnNode.isFunction());
StringBuilder sb = new StringBuilder("/**\n");
JSType type = fnNode.getJSType();
if (type == null || type.isUnknownType()) {
return "";
}
FunctionType funType = type.toMaybeFunctionType();
// We need to use the child nodes of the function as the nodes for the
// parameters of the function type do not have the real parameter names.
// FUNCTION
// NAME
// LP
// NAME param1
// NAME param2
if (fnNode != null) {
Node paramNode = NodeUtil.getFunctionParameters(fnNode).getFirstChild();
// Param types
for (Node n : funType.getParameters()) {
// Bail out if the paramNode is not there.
if (paramNode == null) {
break;
}
sb.append(" * ");
appendAnnotation(sb, "param", getParameterNodeJSDocType(n));
sb.append(" ")
.append(paramNode.getString())
.append("\n");
paramNode = paramNode.getNext();
}
}
// Return type
JSType retType = funType.getReturnType();
if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) {
sb.append(" * ");
appendAnnotation(sb, "return", retType.toAnnotationString());
sb.append("\n");
}
// Constructor/interface
if (funType.isConstructor() || funType.isInterface()) {
FunctionType superConstructor = funType.getSuperClassConstructor();
if (superConstructor != null) {
ObjectType superInstance =
funType.getSuperClassConstructor().getInstanceType();
if (!superInstance.toString().equals("Object")) {
sb.append(" * ");
appendAnnotation(sb, "extends", superInstance.toAnnotationString());
sb.append("\n");
}
}
if (funType.isInterface()) {
for (ObjectType interfaceType : funType.getExtendedInterfaces()) {
sb.append(" * ");
appendAnnotation(sb, "extends", interfaceType.toAnnotationString());
sb.append("\n");
}
}
// Avoid duplicates, add implemented type to a set first
Set<String> interfaces = Sets.newTreeSet();
for (ObjectType interfaze : funType.getImplementedInterfaces()) {
interfaces.add(interfaze.toAnnotationString());
}
for (String interfaze : interfaces) {
sb.append(" * ");
appendAnnotation(sb, "implements", interfaze);
sb.append("\n");
}
if (funType.isConstructor()) {
sb.append(" * @constructor\n");
} else if (funType.isInterface()) {
sb.append(" * @interface\n");
}
}
if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) {
sb.append(" * @javadispatch\n");
}
sb.append(" */\n");
return sb.toString();
}
private void appendAnnotation(StringBuilder sb, String name, String type) {
sb.append("@").append(name).append("
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> parent) {
if (parent.isAssign()) {
Node lValue = parent.getFirstChild();
if (NodeUtil.isGet(lValue)) {
// We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isNominalConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return (lValueType.toMaybeFunctionType()).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.isName()) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isNominalConstructor()) {
return (type.toMaybeFunctionType()).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((ObjectType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
checkConstantProperty(t, n);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> The getprop node.
*/
private void checkConstantProperty(NodeTraversal t,
Node getprop) {
// Check whether the property is modified
Node parent = getprop.getParent();
boolean isDelete = parent.isDelProp();
if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop)
&& !parent.isInc() && !parent.isDec()
&& !isDelete) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName);
// Check whether constant properties are reassigned
if (isConstant) {
if (isDelete) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName));
return;
}
ObjectType oType = objectType;
while (oType != null) {
if (oType.hasReferenceName()) {
if (initializedConstantProperties.containsEntry(
oType.getReferenceName(), propertyName)) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE,
propertyName));
break;
}
}
oType = oType.getImplicitPrototype();
}
Preconditions.checkState(objectType.hasReferenceName());
initializedConstantProperties.put(objectType.getReferenceName(),
propertyName);
// Add the prototype when we're looking at an instance object
if (objectType.isInstanceType()) {
ObjectType prototype = objectType.getImplicitPrototype();
if (prototype != null) {
if (prototype.hasProperty(propertyName)
&& prototype.hasReferenceName()) {
initializedConstantProperties.put(prototype.getReferenceName(),
propertyName);
}
}
}
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType != null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = parent.getJSDocInfo() != null &&
parent.isAssign() &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(propertyName);
if (docInfo != null &&
docInfo.getVisibility() != Visibility.INHERITED) {
break;
}
}
if (objectType == null) {
// We couldn't find a visibility modifier; assume it's public.
return;
}
String referenceSource = getprop.getSourceFileName();
String definingSource = docInfo.getSource
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>precationWarning(
NodeTraversal t, Node n, Node parent) {
// In the global scope, there are only two kinds of accesses that should
// be flagged for warnings:
// 1) Calls of deprecated functions and methods.
// 2) Instantiations of deprecated classes.
// For now, we just let everything else by.
if (t.inGlobalScope()) {
if (!((parent.isCall() && parent.getFirstChild() == n) ||
n.isNew())) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.isGetProp() && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently OK to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.isAssign() &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n, Node parent) {
if (n.isFunction()) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type) {
if (type == null) {
return null;
}
JSDocInfo info = type.getJSDocInfo();
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
ObjectType implicitProto = objType.getImplicitPrototype();
if (implicitProto != null) {
return getTypeDeprecationInfo(implicitProto);
}
}
return null;
}
/**
* Returns if a property is declared constant.
*/
private static boolean isPropertyDeclaredConstant(
ObjectType objectType, String prop) {
for (;
// Only objects with reference names can have constant properties.
objectType != null && objectType.hasReferenceName();
objectType =
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>FlowGraph.Branch>(
controlFlowGraph, new ReachablePredicate()).compute(
controlFlowGraph.getEntry().getValue());
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
private final class ReachablePredicate implements
Predicate<EdgeTuple<Node, ControlFlowGraph.Branch>> {
@Override
public boolean apply(EdgeTuple<Node, Branch> input) {
Branch branch = input.edge;
if (!branch.isConditional()) {
return true;
}
Node predecessor = input.sourceNode;
Node condition = NodeUtil.getConditionExpression(predecessor);
// TODO(user): Handle more complicated expression like true == true,
// etc....
if (condition != null) {
TernaryValue val = NodeUtil.getImpureBooleanValue(condition);
if (val != TernaryValue.UNKNOWN) {
return val.toBoolean(true) == (branch == Branch.ON_TRUE);
}
}
return true;
}
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
private static final long serialVersionUID = 1L;
/** Warnings guards for each suppressible warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressible groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressibleGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
// We only care about function annotations at the FUNCTION and SCRIPT
// level. Otherwise, the @suppress annotation has an implicit
// dependency on the exact structure of our AST, and that seems like
// a bad idea.
if (type == Token.FUNCTION) {
info = NodeUtil.getFunctionJSDocInfo(current);
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
} else if (type == Token.ASSIGN) {
Node rhs = current.getLastChild();
if (rhs.isFunction()) {
info = NodeUtil.getFunctionJSDocInfo(rhs);
}
}
if (info != null) {
for (String suppressor : info.getSuppressions()) {
WarningsGuard guard = suppressors.get(suppressor);
// Some @suppress tags are for other tools, and
// may not have a warnings guard.
if (guard != null) {
CheckLevel newLevel = guard.level(error);
if (newLevel != null) {
return newLevel;
}
}
}
}
}
}
return null;
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.StaticSourceFile;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* NodeUtil contains utilities that get properties from the Node object.
*
*/
public final class NodeUtil {
static final long MAX_POSITIVE_INTEGER_NUMBER = (long)Math.pow(2, 53);
final static String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty";
// TODO(user): Eliminate this class and make all of the static methods
// instance methods of com.google.javascript.rhino.Node.
/** the set of builtin constructors that don't have side effects. */
private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS =
new HashSet<String>(Arrays.asList(
"Array",
"Date",
"Error",
"Object",
"RegExp",
"XMLHttpRequest"));
// Utility class; do not instantiate.
private NodeUtil() {}
/**
* Gets the boolean value of a node that represents a expression. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getImpureBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> literals as well.
switch (n.getType()) {
case Token.STRING:
case Token.STRING_KEY:
return n.getString();
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.NUMBER:
return getStringValue(n.getDouble());
case Token.FALSE:
return "false";
case Token.TRUE:
return "true";
case Token.NULL:
return "null";
case Token.VOID:
return "undefined";
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? "false" : "true"; // reversed.
}
break;
case Token.ARRAYLIT:
return arrayToString(n);
case Token.OBJECTLIT:
return "[object Object]";
}
return null;
}
static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
/**
* When converting arrays to string using Array.prototype.toString or
* Array.prototype.join, the rules for conversion to String are different
* than converting each element individually. Specifically, "null" and
* "undefined" are converted to an empty string.
* @param n A node that is a member of an Array.
* @return The string representation.
*/
static String getArrayElementStringValue(Node n) {
return (NodeUtil.isNullOrUndefined(n) || n.isEmpty())
? "" : getStringValue(n);
}
static String arrayToString(Node literal) {
Node first = literal.getFirstChild();
StringBuilder result = new StringBuilder();
int nextSlot = 0;
int nextSkipSlot = 0;
for (Node n = first; n != null; n = n.getNext()) {
String childValue = getArrayElementStringValue(n);
if (childValue == null) {
return null;
}
if (n != first) {
result.append(',');
}
result.append(childValue);
nextSlot++;
}
return result.toString();
}
/**
* Gets the value of a node as a Number, or null if it cannot be converted.
* When it returns a non-null Double, this method effectively emulates the
* <code>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
if (mayHaveSideEffects(n.getFirstChild())) {
return null;
} else {
return Double.NaN;
}
case Token.NAME:
// Check for known constants
String name = n.getString();
if (name
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING_KEY:
// Return the name of the literal's key.
return parent.getString();
case Token.NUMBER:
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.NOT:
return isImmutableValue(n.getFirstChild());
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if the operator on this node is symmetric
*/
public static boolean isSymmetricOperation(Node n) {
switch (n.getType()) {
case Token.EQ: // equal
case Token.NE: // not equal
case Token.SHEQ: // exactly equal
case Token.SHNE: // exactly not equal
case Token.MUL: // multiply, unlike add it only works on numbers
// or results NaN if any of the operators is not a number
return true;
}
return false;
}
/**
* Returns true if the operator on this node is relational.
* the returned set does not include the equalities.
*/
public static boolean isRelationalOperation(Node n) {
switch (n.getType()) {
case Token.GT: // equal
case Token.GE: // not equal
case Token.LT: // exactly equal
case Token.LE: // exactly not equal
return true;
}
return false;
}
/**
* Returns the inverse of an operator if it is invertible.
* ex. '>' ==> '<'
*/
public static int getInverseOperator(int type) {
switch (type) {
case Token.GT:
return Token.LT;
case Token.LT:
return Token.GT;
case Token.GE:
return Token.LE;
case Token.LE:
return Token.GE;
}
return Token.ERROR;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code>
* If it is evaluated in a
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> different scope, then it
* captures a different variable. Even if the function did not read
* any captured variables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.ARRAYLIT:
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if ((!child.isEmpty()) && !isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Unary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>.isCall()) {
throw new IllegalStateException(
"Expected CALL node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
// Built-in functions with no side effects.
if (nameNode.isName()) {
String name = nameNode.getString();
if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) {
return false;
}
} else if (nameNode.isGetProp()) {
if (callNode.hasOneChild()
&& OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains(
nameNode.getLastChild().getString())) {
return false;
}
if (callNode.isOnlyModifiesThisCall()
&& evaluatesToLocalValue(nameNode.getFirstChild())) {
return false;
}
// Math.floor has no side-effects.
// TODO(nicksantos): This is a terrible terrible hack, until
// I create a definitionProvider that understands namespacing.
if (nameNode.getFirstChild().isName()) {
if ("Math.floor".equals(nameNode.getQualifiedName())) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().isRegExp()
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
} else if (nameNode.getFirstChild().isString()
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.isString() || param.isRegExp()))
return false;
}
}
}
return true;
}
/**
* @return Whether the call has a local result.
*/
static boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.isCall());
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* @return Whether the new has a local result.
*/
static boolean newHasLocalResult(Node n) {
Preconditions.checkState(n.isNew());
return n.isOnlyModifiesThisCall();
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHas
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> GETELEM node?
*/
static boolean isGet(Node n) {
return n.isGetProp() || n.isGetElem();
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.isName() && n.getParent().isVar();
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
if (parent.isVar()) {
return n.getFirstChild();
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.isExprResult()
&& n.getFirstChild().isAssign();
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.isExprResult()
&& n.getFirstChild().isCall();
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.isFor()
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (parent.isFunction()) {
break;
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.isCase() || n.isDefaultCase();
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return n.isName() && !n.getString().isEmpty();
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.isTry() && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Whether the node is a CATCH container BLOCK. */
static boolean isTryCatchNodeContainer(Node n) {
Node parent = n.getParent();
return parent.isTry()
&& parent.getFirstChild().getNext() == n;
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
if (isTryFinallyNode(parent, node)) {
if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) {
// A finally can only be removed if there is a catch.
parent.removeChild(node);
} else {
// Otherwise, only its children can be removed.
node.detachChildren();
}
} else if (node.isCatch()) {
// The CATCH can can only be removed if there is a finally clause.
Node tryNode = node.getParent().getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachFromParent();
} else if (isTryCatchNodeContainer(node)) {
// The container node itself can't be removed, but the contained CATCH
// can if there is a 'finally' clause
Node tryNode = node.getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachChildren();
} else if (node.isBlock()) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (isStatementBlock(parent)
|| isSwitchCase(node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.isVar()) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (parent.isLabel()
&& node == parent.getLastChild()) {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// A LABEL without children can not be referred to, remove it.
removeChild(parent.getParent(), parent);
} else if (parent.isFor()
&& parent.getChildCount() == 4) {
// Only Token.FOR can have an Token.EMPTY other control structure
// need something for the condition. Others need to be replaced
// or
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> the structure removed.
parent.replaceChild(node, IR.empty());
} else {
throw new IllegalStateException("Invalid attempt to remove node: " +
node.toString() + " of "+ parent.toString());
}
}
/**
* Add a finally block if one does not exist.
*/
static void maybeAddFinally(Node tryNode) {
Preconditions.checkState(tryNode.isTry());
if (!NodeUtil.hasFinally(tryNode)) {
tryNode.addChildrenToBack(IR.block().srcref(tryNode));
}
}
/**
* Merge a block with its parent block.
* @return Whether the block was removed.
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.isBlock());
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else {
return false;
}
}
/**
* @param node A node
* @return Whether the call is a NEW or CALL node.
*/
static boolean isCallOrNew(Node node) {
return node.isCall() || node.isNew();
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(fn.isFunction());
return fn.getLastChild();
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.isFunction() && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().isScript()
|| n.getParent().getParent().isFunction());
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isVarOrSimpleAssignLhs(Node n, Node parent) {
return (parent.isAssign() && parent.getFirstChild() == n) ||
parent.isVar();
}
/**
* Determines whether this node is used as an L-value. Notice that sometimes
* names are used as both L-values and R-values.
*
* We treat "var x;" as a pseudo-L-value, which kind of makes sense if you
* treat it as "assignment to 'undefined' at the top of the scope". But if
* we're honest with ourselves, it doesn't make sense, and we only do this
* because it makes sense to treat this as syntactically similar to
* "var x = 0;".
*
* @param n The node
* @return True if n is an L-value.
*/
static boolean isLValue(Node n) {
Preconditions.checkArgument(n.isName() || n.isGetProp() ||
n.isGetElem());
Node parent = n.getParent();
return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| (NodeUtil.isForIn(parent) && parent.getFirstChild() == n)
|| parent.isVar()
|| (parent.isFunction() && parent.getFirstChild() == n)
|| parent.isDec()
|| parent.isInc()
|| parent.isParamList()
|| parent.isCatch();
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
switch (node.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Get the name of an object literal key.
*
* @param key A node
*/
static String getObjectLitKeyName(Node key) {
switch (key.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return key.getString();
}
throw new IllegalStateException("Unexpected node type: " + key);
}
/**
* @param key A OBJECTLIT key node.
* @return The type expected when using the key.
*/
static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) {
if (valueType != null) {
switch (key.getType()) {
case Token.GETTER_DEF:
// GET must always return a function type.
if (valueType.isFunctionType()) {
FunctionType fntype = valueType.toMaybeFunctionType();
valueType = fntype.getReturnType();
} else {
return null;
}
break;
case Token.SETTER_DEF:
if (valueType.isFunctionType())
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>Return true here if the variable includes $$constant in its name.
* </ol>
*
* @param node A NAME or STRING node
* @return True if the variable is constant
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
String name = node.getString();
if (parent.isGetProp() &&
node == parent.getLastChild()) {
return convention.isConstantKey(name);
} else if (isObjectLitKey(node, parent)) {
return convention.isConstantKey(name);
} else {
return convention.isConstant(name);
}
}
/**
* Get the JSDocInfo for a function.
*/
public static JSDocInfo getFunctionJSDocInfo(Node n) {
Preconditions.checkState(n.isFunction());
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.isAssign()) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.isName()) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getSourceFileName();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static StaticSourceFile getSourceFile(Node n) {
StaticSourceFile sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getStaticSourceFile();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The InputId property on the node or its ancestors.
*/
public static InputId getInputId(Node n) {
while (n != null && !n.isScript()) {
n = n.getParent();
}
return (n != null && n.isScript()) ? n.getInputId() : null;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = !isGet(callTarget);
Node call = IR.call(callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case Token.ASSIGN:
// A result that is aliased by a non-local name, is the effectively the
// same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDECR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME:
return isImmutableValue(value) || locals.apply(value);
case Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value)
|| isToStringMethodCall(value)
|| locals.apply(value);
case Token.NEW:
return newHasLocalResult(value)
|| locals.apply(value);
case Token.FUNCTION:
case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.DELPROP:
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> Token.ASSIGN:
return n.getNext();
case Token.VAR:
return n.getFirstChild();
case Token.FUNCTION:
return parent;
}
return null;
}
/** Get the owner of the given l-value node. */
static Node getBestLValueOwner(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue, lValue.getParent())) {
return getBestLValue(lValue.getParent());
} else if (isGet(lValue)) {
return lValue.getFirstChild();
}
return null;
}
/** Get the name of the given l-value node. */
static String getBestLValueName(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue, lValue.getParent())) {
Node owner = getBestLValue(lValue.getParent());
if (owner != null) {
String ownerName = getBestLValueName(owner);
if (ownerName != null) {
return ownerName + "." + getObjectLitKeyName(lValue);
}
}
return null;
}
return lValue.getQualifiedName();
}
/**
* @returns false iff the result of the expression is not consumed.
*/
static boolean isExpressionResultUsed(Node expr) {
// TODO(johnlenz): consider sharing some code with trySimpleUnusedResult.
Node parent = expr.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.EXPR_RESULT:
return false;
case Token.HOOK:
case Token.AND:
case Token.OR:
return (expr == parent.getFirstChild())
? true : isExpressionResultUsed(parent);
case Token.COMMA:
Node gramps = parent.getParent();
if (gramps.isCall() &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval. This we pretend that this is "used".
if (expr == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
expr.getNext().isName() &&
"eval".equals(expr.getNext().getString())) {
return true;
}
}
return (expr == parent.getFirstChild())
? false : isExpressionResultUsed(parent);
case Token.FOR:
if (!NodeUtil.isForIn(parent)) {
// Only an expression whose result is in the condition part of the
// expression is used.
return (parent.getChildAtIndex(1) == expr);
}
break;
}
return true;
}
/**
* @param n The expression to check.
* @return Whether the expression is unconditionally executed only once in the
* containing execution scope.
*/
static boolean isExecutedExactlyOnce(Node n) {
inspect: do {
Node parent =
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> n.getParent();
switch (parent.getType()) {
case Token.IF:
case Token.HOOK:
case Token.AND:
case Token.OR:
if (parent.getFirstChild() != n) {
return false;
}
// other ancestors may be conditional
continue inspect;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
if (parent.getChildAtIndex(1) != n) {
return false;
}
} else {
if (parent.getFirstChild() != n) {
return false;
}
}
// other ancestors may be conditional
continue inspect;
case Token.WHILE:
case Token.DO:
return false;
case Token.TRY:
// Consider all code under a try/catch to be conditionally executed.
if (!hasFinally(parent) || parent.getLastChild() != n) {
return false;
}
continue inspect;
case Token.CASE:
case Token.DEFAULT_CASE:
return false;
case Token.SCRIPT:
case Token.FUNCTION:
// Done, we've reached the scope root.
break inspect;
}
} while ((n = n.getParent()) != null);
return true;
}
static Node booleanNode(boolean value) {
return value ? IR.trueNode() : IR.falseNode();
}
static Node numberNode(double value, Node srcref) {
Node result;
if (Double.isNaN(value)) {
result = IR.name("NaN");
} else if (value == Double.POSITIVE_INFINITY) {
result = IR.name("Infinity");
} else if (value == Double.NEGATIVE_INFINITY) {
result = IR.neg(IR.name("Infinity"));
} else {
result = IR.number(value);
}
if (srcref != null) {
result.srcrefTree(srcref);
}
return result;
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Map;
/**
* CodeGenerator generates codes from a parse tree, sending it to the specified
* CodeConsumer.
*
*/
class CodeGenerator {
// A memoizer for formatting strings as JS strings.
private final Map<String, String> ESCAPED_JS_STRINGS = Maps.newHashMap();
private static final char[] HEX_CHARS
= { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private final CodeConsumer cc;
private final CharsetEncoder outputCharsetEncoder;
CodeGenerator(
CodeConsumer consumer, Charset outputCharset) {
cc = consumer;
if (outputCharset == null || outputCharset == Charsets.US_ASCII) {
// If we want our default (pretending to be UTF-8, but escaping anything
// outside of straight ASCII), then don't use the encoder, but
// just special-case the code. This keeps the normal path through
// the code identical to how it's been for years.
this.outputCharsetEncoder = null;
} else {
this.outputCharsetEncoder = outputCharset.newEncoder();
}
}
CodeGenerator(CodeConsumer consumer) {
this(consumer, null);
}
/**
* Insert a ECMASCRIPT 5 strict annotation.
*/
public void tagAsStrict() {
add("'use strict';");
}
void add(String str) {
cc.add(str);
}
private void addIdentifier(String identifier) {
cc.addIdentifier(identifierEscape(identifier));
}
void add(Node n) {
add(n, Context.OTHER);
}
void add(Node n, Context context) {
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
// Handle associativity.
// e.g. if the parse tree is a * (b * c),
// we can simply generate a * b * c.
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
Preconditions.checkState(first.getNext().isBlock() &&
!first.getNext().hasMoreThanOneChild());
Preconditions.checkState(childCount >= 2 && childCount <= 3);
add("try");
add(first, Context.PRESERVE_BLOCK);
// second child contains the catch block, or nothing if there
// isn't a catch block
Node catchblock = first.getNext().getFirstChild();
if (catchblock != null) {
add(catchblock);
}
if (childCount == 3) {
add("finally");
add(last, Context.PRESERVE_BLOCK);
}
break;
}
case Token.CATCH:
Preconditions.checkState(childCount == 2);
add("catch(");
add(first);
add(")");
add(last, Context.PRESERVE_BLOCK);
break;
case Token.THROW:
Preconditions.checkState(childCount == 1);
add("throw");
add(first);
// Must have a ';' after a throw statement, otherwise safari can't
// parse this.
cc.endStatement(true);
break;
case Token.RETURN:
add("return");
if (childCount == 1) {
add(first);
} else {
Preconditions.checkState(childCount == 0);
}
cc.endStatement();
break;
case Token.VAR:
if (first != null) {
add("var ");
addList(first, false, getContextForNoInOperator(context));
}
break;
case Token.LABEL_NAME:
Preconditions.checkState(!n.getString().isEmpty());
addIdentifier(n.getString());
break;
case Token.NAME:
if (first == null || first.isEmpty())
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> {
addIdentifier(n.getString());
} else {
Preconditions.checkState(childCount == 1);
addIdentifier(n.getString());
cc.addOp("=", true);
if (first.isComma()) {
addExpr(first, NodeUtil.precedence(Token.ASSIGN), Context.OTHER);
} else {
// Add expression, consider nearby code at lowest level of
// precedence.
addExpr(first, 0, getContextForNoInOperator(context));
}
}
break;
case Token.ARRAYLIT:
add("[");
addArrayList(first);
add("]");
break;
case Token.PARAM_LIST:
add("(");
addList(first);
add(")");
break;
case Token.COMMA:
Preconditions.checkState(childCount == 2);
unrollBinaryOperator(n, Token.COMMA, ",", context, Context.OTHER, 0, 0);
break;
case Token.NUMBER:
Preconditions.checkState(childCount == 0);
cc.addNumber(n.getDouble());
break;
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS: {
// All of these unary operators are right-associative
Preconditions.checkState(childCount == 1);
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type), Context.OTHER);
break;
}
case Token.NEG: {
Preconditions.checkState(childCount == 1);
// It's important to our sanity checker that the code
// we print produces the same AST as the code we parse back.
// NEG is a weird case because Rhino parses "- -2" as "2".
if (n.getFirstChild().isNumber()) {
cc.addNumber(-n.getFirstChild().getDouble());
} else {
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type), Context.OTHER);
}
break;
}
case Token.HOOK: {
Preconditions.checkState(childCount == 3);
int p = NodeUtil.precedence(type);
addExpr(first, p + 1, context);
cc.addOp("?", true);
addExpr(first.getNext(), 1, Context.OTHER);
cc.addOp(":", true);
addExpr(last, 1, Context.OTHER);
break;
}
case Token.REGEXP:
if (!first.isString() ||
!last.isString()) {
throw new Error("Expected children to be strings");
}
String regexp = regexpEscape(first.getString(), outputCharsetEncoder);
// I only use one .add because whitespace matters
if (childCount == 2) {
add(regexp + last.getString());
} else {
Preconditions.checkState(childCount == 1);
add(regexp);
}
break;
case Token.FUNCTION:
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
Preconditions.checkState(childCount == 3);
boolean funcNeedsPare
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>ns = (context == Context.START_OF_EXPR);
if (funcNeedsParens) {
add("(");
}
add("function");
add(first);
add(first.getNext());
add(last, Context.PRESERVE_BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.GETTER_DEF:
case Token.SETTER_DEF:
Preconditions.checkState(n.getParent().isObjectLit());
Preconditions.checkState(childCount == 1);
Preconditions.checkState(first.isFunction());
// Get methods are unnamed
Preconditions.checkState(first.getFirstChild().getString().isEmpty());
if (type == Token.GETTER_DEF) {
// Get methods have no parameters.
Preconditions.checkState(!first.getChildAtIndex(1).hasChildren());
add("get ");
} else {
// Set methods have one parameter.
Preconditions.checkState(first.getChildAtIndex(1).hasOneChild());
add("set ");
}
// The name is on the GET or SET node.
String name = n.getString();
Node fn = first;
Node parameters = fn.getChildAtIndex(1);
Node body = fn.getLastChild();
// Add the property name.
if (!n.isQuotedString() &&
TokenStream.isJSIdentifier(name) &&
// do not encode literally any non-literal characters that were
// Unicode escaped.
NodeUtil.isLatin(name)) {
add(name);
} else {
// Determine if the string is a simple number.
double d = getSimpleNumber(name);
if (!Double.isNaN(d)) {
cc.addNumber(d);
} else {
addJsString(n);
}
}
add(parameters);
add(body, Context.PRESERVE_BLOCK);
break;
case Token.SCRIPT:
case Token.BLOCK: {
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
boolean preserveBlock = context == Context.PRESERVE_BLOCK;
if (preserveBlock) {
cc.beginBlock();
}
boolean preferLineBreaks =
type == Token.SCRIPT ||
(type == Token.BLOCK &&
!preserveBlock &&
n.getParent() != null &&
n.getParent().isScript());
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.STATEMENT);
// VAR doesn't include ';' since it gets used in expressions
if (c.isVar()) {
cc.endStatement();
}
if (c.isFunction()) {
cc.maybeLineBreak();
}
// Prefer to break lines in between top-level statements
// because top-level statements are more homogeneous.
if (preferLineBreaks) {
cc.notePreferredLineBreak();
}
}
if (preserveBlock) {
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
}
break;
}
case Token.FOR:
if (childCount == 4) {
add("for(");
if (first.isVar())
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> {
add(first, Context.IN_FOR_INIT_CLAUSE);
} else {
addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE);
}
add(";");
add(first.getNext());
add(";");
add(first.getNext().getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
Preconditions.checkState(childCount == 3);
add("for(");
add(first);
add("in");
add(first.getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
}
break;
case Token.DO:
Preconditions.checkState(childCount == 2);
add("do");
addNonEmptyStatement(first, Context.OTHER, false);
add("while(");
add(last);
add(")");
cc.endStatement();
break;
case Token.WHILE:
Preconditions.checkState(childCount == 2);
add("while(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.EMPTY:
Preconditions.checkState(childCount == 0);
break;
case Token.GETPROP: {
Preconditions.checkState(
childCount == 2,
"Bad GETPROP: expected 2 children, but got %s", childCount);
Preconditions.checkState(
last.isString(),
"Bad GETPROP: RHS should be STRING");
boolean needsParens = (first.isNumber());
if (needsParens) {
add("(");
}
addExpr(first, NodeUtil.precedence(type), context);
if (needsParens) {
add(")");
}
add(".");
addIdentifier(last.getString());
break;
}
case Token.GETELEM:
Preconditions.checkState(
childCount == 2,
"Bad GETELEM: expected 2 children but got %s", childCount);
addExpr(first, NodeUtil.precedence(type), context);
add("[");
add(first.getNext());
add("]");
break;
case Token.WITH:
Preconditions.checkState(childCount == 2);
add("with(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.INC:
case Token.DEC: {
Preconditions.checkState(childCount == 1);
String o = type == Token.INC ? "++" : "--";
int postProp = n.getIntProp(Node.INCRDECR_PROP);
// A non-zero post-prop value indicates a post inc/dec, default of zero
// is a pre-inc/dec.
if (postProp != 0) {
addExpr(first, NodeUtil.precedence(type), context);
cc.addOp(o, false);
} else {
cc.addOp(o, false);
add(first);
}
break;
}
case Token.CALL:
// We have two special cases here:
//
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> 1) If the left hand side of the call is a direct reference to eval,
// then it must have a DIRECT_EVAL annotation. If it does not, then
// that means it was originally an indirect call to eval, and that
// indirectness must be preserved.
// 2) If the left hand side of the call is a property reference,
// then the call must not a FREE_CALL annotation. If it does, then
// that means it was originally an call without an explicit this and
// that must be preserved.
if (isIndirectEval(first)
|| n.getBooleanProp(Node.FREE_CALL) && NodeUtil.isGet(first)) {
add("(0,");
addExpr(first, NodeUtil.precedence(Token.COMMA), Context.OTHER);
add(")");
} else {
addExpr(first, NodeUtil.precedence(type), context);
}
add("(");
addList(first.getNext());
add(")");
break;
case Token.IF:
boolean hasElse = childCount == 3;
boolean ambiguousElseClause =
context == Context.BEFORE_DANGLING_ELSE && !hasElse;
if (ambiguousElseClause) {
cc.beginBlock();
}
add("if(");
add(first);
add(")");
if (hasElse) {
addNonEmptyStatement(
first.getNext(), Context.BEFORE_DANGLING_ELSE, false);
add("else");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
addNonEmptyStatement(first.getNext(), Context.OTHER, false);
Preconditions.checkState(childCount == 2);
}
if (ambiguousElseClause) {
cc.endBlock();
}
break;
case Token.NULL:
Preconditions.checkState(childCount == 0);
cc.addConstant("null");
break;
case Token.THIS:
Preconditions.checkState(childCount == 0);
add("this");
break;
case Token.FALSE:
Preconditions.checkState(childCount == 0);
cc.addConstant("false");
break;
case Token.TRUE:
Preconditions.checkState(childCount == 0);
cc.addConstant("true");
break;
case Token.CONTINUE:
Preconditions.checkState(childCount <= 1);
add("continue");
if (childCount == 1) {
if (!first.isLabelName()) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.DEBUGGER:
Preconditions.checkState(childCount == 0);
add("debugger");
cc.endStatement();
break;
case Token.BREAK:
Preconditions.checkState(childCount <= 1);
add("break");
if (childCount == 1) {
if (!first.isLabelName()) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.EXPR_RESULT:
Preconditions.checkState(childCount == 1);
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> add(first, Context.START_OF_EXPR);
cc.endStatement();
break;
case Token.NEW:
add("new ");
int precedence = NodeUtil.precedence(type);
// If the first child contains a CALL, then claim higher precedence
// to force parentheses. Otherwise, when parsed, NEW will bind to the
// first viable parentheses (don't traverse into functions).
if (NodeUtil.containsType(
first, Token.CALL, NodeUtil.MATCH_NOT_FUNCTION)) {
precedence = NodeUtil.precedence(first.getType()) + 1;
}
addExpr(first, precedence, Context.OTHER);
// '()' is optional when no arguments are present
Node next = first.getNext();
if (next != null) {
add("(");
addList(next);
add(")");
}
break;
case Token.STRING_KEY:
Preconditions.checkState(
childCount == 1, "Object lit key must have 1 child");
addJsString(n);
break;
case Token.STRING:
Preconditions.checkState(
childCount == 0, "A string may not have children");
addJsString(n);
break;
case Token.DELPROP:
Preconditions.checkState(childCount == 1);
add("delete ");
add(first);
break;
case Token.OBJECTLIT: {
boolean needsParens = (context == Context.START_OF_EXPR);
if (needsParens) {
add("(");
}
add("{");
for (Node c = first; c != null; c = c.getNext()) {
if (c != first) {
cc.listSeparator();
}
if (c.isGetterDef() || c.isSetterDef()) {
add(c);
} else {
Preconditions.checkState(c.isStringKey());
String key = c.getString();
// Object literal property names don't have to be quoted if they
// are not JavaScript keywords
if (!c.isQuotedString() &&
!TokenStream.isKeyword(key) &&
TokenStream.isJSIdentifier(key) &&
// do not encode literally any non-literal characters that
// were Unicode escaped.
NodeUtil.isLatin(key)) {
add(key);
} else {
// Determine if the string is a simple number.
double d = getSimpleNumber(key);
if (!Double.isNaN(d)) {
cc.addNumber(d);
} else {
addExpr(c, 1, Context.OTHER);
}
}
add(":");
addExpr(c.getFirstChild(), 1, Context.OTHER);
}
}
add("}");
if (needsParens) {
add(")");
}
break;
}
case Token.SWITCH:
add("switch(");
add(first);
add(")");
cc.beginBlock();
addAllSiblings(first.getNext());
cc.endBlock(context == Context.STATEMENT);
break;
case Token.CASE:
Preconditions.checkState(childCount == 2);
add("case ");
add(first);
addCaseBody(last);
break;
case Token.DEFAULT_CASE:
Preconditions.checkState
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>(childCount == 1);
add("default");
addCaseBody(first);
break;
case Token.LABEL:
Preconditions.checkState(childCount == 2);
if (!first.isLabelName()) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(first);
add(":");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), true);
break;
default:
throw new Error("Unknown type " + type + "\n" + n.toStringTree());
}
cc.endSourceMapping(n);
}
/**
* We could use addList recursively here, but sometimes we produce
* very deeply nested operators and run out of stack space, so we
* just unroll the recursion when possible.
*
* We assume nodes are left-recursive.
*/
private void unrollBinaryOperator(
Node n, int op, String opStr, Context context,
Context rhsContext, int leftPrecedence, int rightPrecedence) {
Node firstNonOperator = n.getFirstChild();
while (firstNonOperator.getType() == op) {
firstNonOperator = firstNonOperator.getFirstChild();
}
addExpr(firstNonOperator, leftPrecedence, context);
Node current = firstNonOperator;
do {
current = current.getParent();
cc.addOp(opStr, true);
addExpr(current.getFirstChild().getNext(), rightPrecedence, rhsContext);
} while (current != n);
}
static boolean isSimpleNumber(String s) {
int len = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c < '0' || c > '9') {
return false;
}
}
return len > 0 && s.charAt(0) != '0';
}
static double getSimpleNumber(String s) {
if (isSimpleNumber(s)) {
try {
long l = Long.parseLong(s);
if (l < NodeUtil.MAX_POSITIVE_INTEGER_NUMBER) {
return l;
}
} catch (NumberFormatException e) {
// The number was too long to parse. Fall through to NaN.
}
}
return Double.NaN;
}
/**
* @return Whether the name is an indirect eval.
*/
private boolean isIndirectEval(Node n) {
return n.isName() && "eval".equals(n.getString()) &&
!n.getBooleanProp(Node.DIRECT_EVAL);
}
/**
* Adds a block or expression, substituting a VOID with an empty statement.
* This is used for "for (...);" and "if (...);" type statements.
*
* @param n The node to print.
* @param context The context to determine how the node should be printed.
*/
private void addNonEmptyStatement(
Node n, Context context, boolean allowNonBlockChild) {
Node nodeToProcess = n;
if (!allowNonBlockChild && !n.isBlock()) {
throw new Error("Missing BLOCK child.");
}
// Strip unneeded blocks, that is blocks with <2 children unless
// the CodePrinter specifically wants to keep them.
if
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> (n.isBlock()) {
int count = getNonEmptyChildCount(n, 2);
if (count == 0) {
if (cc.shouldPreserveExtraBlocks()) {
cc.beginBlock();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
} else {
cc.endStatement(true);
}
return;
}
if (count == 1) {
// Hack around a couple of browser bugs:
// Safari needs a block around function declarations.
// IE6/7 needs a block around DOs.
Node firstAndOnlyChild = getFirstNonEmptyChild(n);
boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks();
if (alwaysWrapInBlock || isOneExactlyFunctionOrDo(firstAndOnlyChild)) {
cc.beginBlock();
add(firstAndOnlyChild, Context.STATEMENT);
cc.maybeLineBreak();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
return;
} else {
// Continue with the only child.
nodeToProcess = firstAndOnlyChild;
}
}
if (count > 1) {
context = Context.PRESERVE_BLOCK;
}
}
if (nodeToProcess.isEmpty()) {
cc.endStatement(true);
} else {
add(nodeToProcess, context);
// VAR doesn't include ';' since it gets used in expressions - so any
// VAR in a statement context needs a call to endStatement() here.
if (nodeToProcess.isVar()) {
cc.endStatement();
}
}
}
/**
* @return Whether the Node is a DO or FUNCTION (with or without
* labels).
*/
private boolean isOneExactlyFunctionOrDo(Node n) {
if (n.isLabel()) {
Node labeledStatement = n.getLastChild();
if (!labeledStatement.isBlock()) {
return isOneExactlyFunctionOrDo(labeledStatement);
} else {
// For labels with block children, we need to ensure that a
// labeled FUNCTION or DO isn't generated when extraneous BLOCKs
// are skipped.
if (getNonEmptyChildCount(n, 2) == 1) {
return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n));
} else {
// Either a empty statement or an block with more than one child,
// way it isn't a FUNCTION or DO.
return false;
}
}
} else {
return (n.isFunction() || n.isDo());
}
}
private void addExpr(Node n, int minPrecedence, Context context) {
if ((NodeUtil.precedence(n.getType()) < minPrecedence) ||
((context == Context.IN_FOR_INIT_CLAUSE) &&
(n.isIn()))){
add("(");
add(n, clearContextForNoInOperator(context));
add(")");
} else {
add(n, context);
}
}
void addList(Node firstInList) {
addList(firstInList, true, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument) {
addList(firstInList
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> sb.append(c);
}
break;
default:
// If we're given an outputCharsetEncoder, then check if the
// character can be represented in this character set.
if (outputCharsetEncoder != null) {
if (outputCharsetEncoder.canEncode(c)) {
sb.append(c);
} else {
// Unicode-escape the character.
appendHexJavaScriptRepresentation(sb, c);
}
} else {
// No charsetEncoder provided - pass straight Latin characters
// through, and escape the rest. Doing the explicit character
// check is measurably faster than using the CharsetEncoder.
if (c > 0x1f && c < 0x7f) {
sb.append(c);
} else {
// Other characters can be misinterpreted by some JS parsers,
// or perhaps mangled by proxies along the way,
// so we play it safe and Unicode escape them.
appendHexJavaScriptRepresentation(sb, c);
}
}
}
}
sb.append(quote);
return sb.toString();
}
static String identifierEscape(String s) {
// First check if escaping is needed at all -- in most cases it isn't.
if (NodeUtil.isLatin(s)) {
return s;
}
// Now going through the string to escape non-Latin characters if needed.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// Identifiers should always go to Latin1/ ASCII characters because
// different browser's rules for valid identifier characters are
// crazy.
if (c > 0x1F && c < 0x7F) {
sb.append(c);
} else {
appendHexJavaScriptRepresentation(sb, c);
}
}
return sb.toString();
}
/**
* @param maxCount The maximum number of children to look for.
* @return The number of children of this node that are non empty up to
* maxCount.
*/
private static int getNonEmptyChildCount(Node n, int maxCount) {
int i = 0;
Node c = n.getFirstChild();
for (; c != null && i < maxCount; c = c.getNext()) {
if (c.isBlock()) {
i += getNonEmptyChildCount(c, maxCount-i);
} else if (!c.isEmpty()) {
i++;
}
}
return i;
}
/** Gets the first non-empty child of the given node. */
private static Node getFirstNonEmptyChild(Node n) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.isBlock()) {
Node result = getFirstNonEmptyChild(c);
if (result != null) {
return result;
}
} else if (!c.isEmpty()) {
return c;
}
}
return null;
}
// Information on the current context. Used for disambiguating special cases.
// For example, a "{" could indicate the start of an object literal or a
// block, depending on the current context.
enum Context {
STATEMENT
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> null) {
if (methodName.equals("inherits")) {
return SubclassType.INHERITS;
} else if (methodName.equals("mixin")) {
return SubclassType.MIXIN;
}
}
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return "superClass_".equals(propertyName) ||
super.isSuperClassReference(propertyName);
}
/**
* Given a qualified name node, returns whether "prototype" is at the end.
* For example:
* a.b.c => false
* a.b.c.prototype => true
*/
private boolean endsWithPrototype(Node qualifiedName) {
return qualifiedName.isGetProp() &&
qualifiedName.getLastChild().getString().equals("prototype");
}
/**
* Extracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Extracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.isGetProp()) {
String qualifiedName = callee.getQualifiedName();
if (functionName.equals(qualifiedName)) {
Node target = callee.getNext();
if (target != null && target.isString()) {
className = target.getString();
}
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.isArrayLit()) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.isString()) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return super.identifyTypeDeclarationCall(n);
}
@Override
public String getAbstractMethodName() {
return "goog.abstractMethod";
}
@Override
public String get
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>:
case Token.CATCH:
case Token.LABEL:
return n != parent.getFirstChild();
case Token.FUNCTION:
return n == parent.getFirstChild().getNext().getNext();
case Token.CONTINUE:
case Token.BREAK:
case Token.EXPR_RESULT:
case Token.VAR:
case Token.RETURN:
case Token.THROW:
return false;
case Token.TRY:
/* Just before we are about to visit the second child of the TRY node,
* we know that we will be visiting either the CATCH or the FINALLY.
* In other words, we know that the post order traversal of the TRY
* block has been finished, no more exceptions can be caught by the
* handler at this TRY block and should be taken out of the stack.
*/
if (n == parent.getFirstChild().getNext()) {
Preconditions.checkState(exceptionHandler.peek() == parent);
exceptionHandler.pop();
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.IF:
handleIf(n);
return;
case Token.WHILE:
handleWhile(n);
return;
case Token.DO:
handleDo(n);
return;
case Token.FOR:
handleFor(n);
return;
case Token.SWITCH:
handleSwitch(n);
return;
case Token.CASE:
handleCase(n);
return;
case Token.DEFAULT_CASE:
handleDefault(n);
return;
case Token.BLOCK:
case Token.SCRIPT:
handleStmtList(n);
return;
case Token.FUNCTION:
handleFunction(n);
return;
case Token.EXPR_RESULT:
handleExpr(n);
return;
case Token.THROW:
handleThrow(n);
return;
case Token.TRY:
handleTry(n);
return;
case Token.CATCH:
handleCatch(n);
return;
case Token.BREAK:
handleBreak(n);
return;
case Token.CONTINUE:
handleContinue(n);
return;
case Token.RETURN:
handleReturn(n);
return;
case Token.WITH:
handleWith(n);
return;
case Token.LABEL:
return;
default:
handleStmt(n);
return;
}
}
private void handleIf(Node node) {
Node thenBlock = node.getFirstChild().getNext();
Node elseBlock = thenBlock.getNext();
createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock));
if (elseBlock == null) {
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this)); // not taken branch
} else {
createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock));
}
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleWhile(Node node) {
// Control goes to the first statement if the condition evaluates to true.
createEdge(node, Branch.ON_TRUE,
computeFallThrough(node.getFirstChild().getNext()));
// Control goes to the follow() if the
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> condition evaluates to false.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleDo(Node node) {
// The first edge can be the initial iteration as well as the iterations
// after.
createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild()));
// The edge that leaves the do loop if the condition fails.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleFor(Node forNode) {
if (forNode.getChildCount() == 4) {
// We have for (init; cond; iter) { body }
Node init = forNode.getFirstChild();
Node cond = init.getNext();
Node iter = cond.getNext();
Node body = iter.getNext();
// After initialization, we transfer to the FOR which is in charge of
// checking the condition (for the first time).
createEdge(init, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
// The end of the body will have a unconditional branch to our iter
// (handled by calling computeFollowNode of the last instruction of the
// body. Our iter will jump to the forNode again to another condition
// check.
createEdge(iter, Branch.UNCOND, forNode);
connectToPossibleExceptionHandler(init, init);
connectToPossibleExceptionHandler(forNode, cond);
connectToPossibleExceptionHandler(iter, iter);
} else {
// We have for (item in collection) { body }
Node item = forNode.getFirstChild();
Node collection = item.getNext();
Node body = collection.getNext();
// The collection behaves like init.
createEdge(collection, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
connectToPossibleExceptionHandler(forNode, collection);
}
}
private void handleSwitch(Node node) {
// Transfer to the first non-DEFAULT CASE. if there are none, transfer
// to the DEFAULT or the EMPTY node.
Node next = getNextSiblingOfType(
node.getFirstChild().getNext(), Token.CASE, Token.EMPTY);
if (next != null) { // Has at least one CASE or EMPTY
createEdge(node, Branch.UNCOND, next);
} else { // Has no CASE but possibly a DEFAULT
if (node.getFirstChild().getNext() != null) {
createEdge(node, Branch.UNCOND, node.getFirstChild().getNext());
} else { // No CASE, no DEFAULT
createEdge(node, Branch.UNCOND
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleCase(Node node) {
// Case is a bit tricky....First it goes into the body if condition is true.
createEdge(node, Branch.ON_TRUE,
node.getFirstChild().getNext());
// Look for the next CASE, skipping over DEFAULT.
Node next = getNextSiblingOfType(node.getNext(), Token.CASE);
if (next != null) { // Found a CASE
Preconditions.checkState(next.isCase());
createEdge(node, Branch.ON_FALSE, next);
} else { // No more CASE found, go back and search for a DEFAULT.
Node parent = node.getParent();
Node deflt = getNextSiblingOfType(
parent.getFirstChild().getNext(), Token.DEFAULT_CASE);
if (deflt != null) { // Has a DEFAULT
createEdge(node, Branch.ON_FALSE, deflt);
} else { // No DEFAULT found, go to the follow of the SWITCH.
createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmtList(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.isBlock() && parent != null &&
parent.isTry() &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since control doesn't go into that
// function (unless it is called)
while (child != null && child.isFunction()) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT_CASE:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.isBlock() && node.isSyntheticBlock()) {
createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this));
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
Node parent = node.getParent();
/*
* Continuously look up the ancestor tree for the BREAK target or the target
* with the corresponding label and connect to it. If along the path we
* discover a FINALLY, we will connect the BREAK to that FINALLY. From then
* on, we will just record the control flow changes in the finallyMap. This
* is due to the fact that we need to connect any node that leaves its own
* FINALLY block to the outer FINALLY or the BREAK's target but those nodes
* are not known yet due to the way we traverse the nodes.
*/
for (cur = node, lastJump = node;
!isBreakTarget(cur, label);
cur = parent, parent = parent.getParent()) {
if (cur.isTry() && NodeUtil.hasFinally(cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFallThrough(
cur.getLastChild()));
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
if (parent == null) {
if (compiler.isIdeMode()) {
// In IDE mode, we expect that the data flow graph may
// not be well-formed.
return;
} else {
throw new IllegalStateException("Cannot find break target.");
}
}
previous = cur;
}
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this));
} else {
finallyMap.put(lastJump, computeFollowNode(cur, this));
}
}
private void handleContinue(Node node) {
String label = null;
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
// Similar to handBreak's logic with a few minor variation.
Node parent = node.getParent();
for (cur = node, lastJump = node;
!isContinueTarget(cur, parent, label);
cur = parent, parent = parent.getParent()) {
if (cur.isTry() && NodeUtil.hasFinally(
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, cur.getLastChild());
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find continue target.");
previous = cur;
}
Node iter = cur;
if (cur.getChildCount() == 4) {
iter = cur.getFirstChild().getNext().getNext();
}
if (lastJump == node) {
createEdge(node, Branch.UNCOND, iter);
} else {
finallyMap.put(lastJump, iter);
}
}
private void handleReturn(Node node) {
Node lastJump = null;
for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) {
Node curHandler = iter.next();
if (curHandler.isFunction()) {
break;
}
if (NodeUtil.hasFinally(curHandler)) {
if (lastJump == null) {
createEdge(node, Branch.UNCOND, curHandler.getLastChild());
} else {
finallyMap.put(lastJump,
computeFallThrough(curHandler.getLastChild()));
}
lastJump = curHandler;
}
}
if (node.hasChildren()) {
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
if (lastJump == null) {
createEdge(node, Branch.UNCOND, null);
} else {
finallyMap.put(lastJump, null);
}
}
private void handleStmt(Node node) {
// Simply transfer to the next line.
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) {
return computeFollowNode(node, node, cfa);
}
static Node computeFollowNode(Node node) {
return computeFollowNode(node, node, null);
}
/**
* Computes the follow() node of a given node and its parent. There is a side
* effect when calling this function. If this function computed an edge that
* exists a FINALLY, it'll attempt to connect the fromNode to the outer
* FINALLY according to the finallyMap.
*
* @param fromNode The original source node since {@code node} is changed
* during recursion.
* @param node The node that follow() should compute.
*/
private static Node computeFollowNode(
Node fromNode, Node node, ControlFlowAnalysis cfa) {
/*
* This is the case where:
*
* 1. Parent is null implies that we are transferring control to the end of
* the script.
*
* 2. Parent is a function implies that we are transferring control back to
* the caller of the function.
*
* 3. If the node is a return statement, we should also transfer control
* back to the caller of the function.
*
* 4. If the node is root then we have reached the end of what we have been
* asked to traverse.
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> *
* In all cases we should transfer control to a "symbolic return" node.
* This will make life easier for DFAs.
*/
Node parent = node.getParent();
if (parent == null || parent.isFunction() ||
(cfa != null && node == cfa.root)) {
return null;
}
// If we are just before a IF/WHILE/DO/FOR:
switch (parent.getType()) {
// The follow() of any of the path from IF would be what follows IF.
case Token.IF:
return computeFollowNode(fromNode, parent, cfa);
case Token.CASE:
case Token.DEFAULT_CASE:
// After the body of a CASE, the control goes to the body of the next
// case, without having to go to the case condition.
if (parent.getNext() != null) {
if (parent.getNext().isCase()) {
return parent.getNext().getFirstChild().getNext();
} else if (parent.getNext().isDefaultCase()) {
return parent.getNext().getFirstChild();
} else {
Preconditions.checkState(false, "Not reachable");
}
} else {
return computeFollowNode(fromNode, parent, cfa);
}
break;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
return parent;
} else {
return parent.getFirstChild().getNext().getNext();
}
case Token.WHILE:
case Token.DO:
return parent;
case Token.TRY:
// If we are coming out of the TRY block...
if (parent.getFirstChild() == node) {
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(parent.getLastChild());
} else { // and have no FINALLY.
return computeFollowNode(fromNode, parent, cfa);
}
// CATCH block.
} else if (NodeUtil.getCatchBlock(parent) == node){
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(node.getNext());
} else {
return computeFollowNode(fromNode, parent, cfa);
}
// If we are coming out of the FINALLY block...
} else if (parent.getLastChild() == node){
if (cfa != null) {
for (Node finallyNode : cfa.finallyMap.get(parent)) {
cfa.createEdge(fromNode, Branch.UNCOND, finallyNode);
}
}
return computeFollowNode(fromNode, parent, cfa);
}
}
// Now that we are done with the special cases follow should be its
// immediate sibling, unless its sibling is a function
Node nextSibling = node.getNext();
// Skip function declarations because control doesn't get pass into it.
while (nextSibling != null && nextSibling.isFunction()) {
nextSibling = nextSibling.getNext();
}
if (nextSibling != null) {
return computeFallThrough(nextSibling);
} else {
// If there are no more siblings, control is transferred up the AST.
return computeFollowNode(fromNode, parent, cfa);
}
}
/**
* Computes the destination node of n when we want
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> to fallthrough into the
* subtree of n. We don't always create a CFG edge into n itself because of
* DOs and FORs.
*/
static Node computeFallThrough(Node n) {
switch (n.getType()) {
case Token.DO:
return computeFallThrough(n.getFirstChild());
case Token.FOR:
if (NodeUtil.isForIn(n)) {
return n.getFirstChild().getNext();
}
return computeFallThrough(n.getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (handler.isFunction()) {
return;
}
Preconditions.checkState(handler.isTry());
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types) {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(), label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.isLabel()) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* Get the TRY block with a CATCH that would be run if n throws an exception.
* @return The CATCH node or null if it there isn't a CATCH before the
* the function terminates.
*/
static Node getExceptionHandler(Node n) {
for (Node cur = n;
!cur.isScript() && !cur.isFunction();
cur = cur.getParent()) {
Node catchNode = getCatchHandlerForBlock(cur);
if (catchNode != null) {
return catchNode;
}
}
return null;
}
/**
* Locate the catch BLOCK given the first block in a TRY.
* @return The CATCH node or null there is no catch handler.
*/
static Node getCatchHandlerForBlock(Node block) {
if (block.isBlock() &&
block.getParent().isTry() &&
block.getParent().getFirstChild() == block) {
for (Node s = block.getNext(); s != null; s = s.getNext()) {
if (NodeUtil.hasCatchHandler(s)) {
return s.getFirstChild();
}
}
}
return null;
}
/**
* A {@link ControlFlowGraph} which provides
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> (in particular, if not overridden)
*/
public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/** Exception related. */
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that is also represented by another control flow graph
* node.
*
* <p>For example, traversing an IF node as root will visit the two subtree
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edge.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// is bleed into the local scope and parameters has been assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body represent by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// Theses control structure is represented by its node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
// The FOR(;;) node differs from other control structure in that
// it has a initialization and a increment statement. Those
// two statements have its corresponding CFG nodes to represent them.
// The FOR node represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++; }
if (NodeUtil.isForIn(parent)) {
// TODO(user): Investigate how we should handle the case where
// we have a very complex expression inside the FOR-IN header.
return n != parent
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>.getFirstChild();
} else {
return NodeUtil.getConditionExpression(parent) != n;
}
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.WITH:
return n != parent.getFirstChild();
default:
return false;
}
}
}
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>) {
// Constructing the global scope is very different than constructing
// inner scopes, because only global scopes can contain named classes that
// show up in the type registry.
Scope newScope = null;
AbstractScopeBuilder scopeBuilder = null;
if (parent == null) {
// Run a first-order analysis over the syntax tree.
(new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults))
.process(root.getFirstChild(), root.getLastChild());
// Find all the classes in the global scope.
newScope = createInitialScope(root);
GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope);
scopeBuilder = globalScopeBuilder;
NodeTraversal.traverse(compiler, root, scopeBuilder);
} else {
newScope = new Scope(parent, root);
LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope);
scopeBuilder = localScopeBuilder;
localScopeBuilder.build();
}
scopeBuilder.resolveStubDeclarations();
scopeBuilder.resolveTypes();
// Gather the properties in each function that we found in the
// global scope, if that function has a @this type that we can
// build properties on.
for (Node functionNode : scopeBuilder.nonExternFunctions) {
JSType type = functionNode.getJSType();
if (type != null && type.isFunctionType()) {
FunctionType fnType = type.toMaybeFunctionType();
ObjectType fnThisType = fnType.getTypeOfThis();
if (!fnThisType.isUnknownType()) {
NodeTraversal.traverse(compiler, functionNode.getLastChild(),
scopeBuilder.new CollectProperties(fnThisType));
}
}
}
if (parent == null) {
codingConvention.defineDelegateProxyPrototypeProperties(
typeRegistry, newScope, delegateProxyPrototypes,
delegateCallingConventions);
}
return newScope;
}
/**
* Patches a given global scope by removing variables previously declared in
* a script and re-traversing a new version of that script.
*
* @param globalScope The global scope generated by {@code createScope}.
* @param scriptRoot The script that is modified.
*/
void patchGlobalScope(Scope globalScope, Node scriptRoot) {
// Preconditions: This is supposed to be called only on (named) SCRIPT nodes
// and a global typed scope should have been generated already.
Preconditions.checkState(scriptRoot.isScript());
Preconditions.checkNotNull(globalScope);
Preconditions.checkState(globalScope.isGlobal());
String scriptName = NodeUtil.getSourceName(scriptRoot);
Preconditions.checkNotNull(scriptName);
for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) {
if (scriptName.equals(NodeUtil.getSourceName(node))) {
functionAnalysisResults.remove(node);
}
}
(new FirstOrderFunctionAnalyzer(
compiler, functionAnalysisResults)).process(null, scriptRoot);
// TODO(bashir): Variable declaration is not the only side effect of last
// global scope generation but here we only wipe that part off!
// Remove all variables that were previously declared in this scripts.
// First find all vars to remove then remove them because of iterator!
Iterator<Var> varIter = globalScope.getVars();
List<Var> varsToRemove
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, child.getFirstChild(),
NodeUtil.getBestJSDocInfo(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.isAssign()) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getLastChild(),
firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, null, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, Node valueNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
}
private JSType getNativeType(JSTypeNative nativeType) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're building.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Object literals with a @lends annotation aren't analyzed until we
* reach the root of the statement they're defined in.
*
* This ensures that if there are any @lends annotations on the object
* literals, the type on the @lends annotation resolves correctly.
*
* For more information, see
* http://code.google.com/p/closure-compiler/issues/detail?id=314
*/
private List<Node> lentObjectLiterals = null;
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
/**
* The InputId of the current node.
*/
private InputId inputId;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n,
Node parent) {
inputId = t.getInputId();
if (n.isFunction() ||
n.isScript()) {
Preconditions.checkNotNull(inputId);
sourceName = NodeUtil.getSourceName(n);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
boolean descend = parent == null || !parent.isFunction() ||
n == parent.getFirstChild() || parent == scope.getRootNode();
if (descend) {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child, n);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
attachLiteralTypes(t, n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n, parent);
checkForCallingConventionDefiningCalls(n, delegateCallingConventions);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// Hoisted functions are handled during pre-traversal.
if (!NodeUtil.isHoistedFunctionDeclaration(n)) {
defineFunctionLiteral(n, parent);
}
break;
case Token.ASSIGN:
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.isGetProp() &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n, parent);
break;
case Token.VAR:
defineVar(n, parent);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.isExprResult() &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
// Analyze any @lends object literals in this statement.
if (n.getParent() != null && NodeUtil.isStatement(n) &&
lentObjectLiterals != null) {
for (Node objLit : lentObjectLiterals) {
defineObjectLiteral(objLit);
}
lentObjectLiterals.clear();
}
}
private void attachLiteralTypes(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>VOID_TYPE));
break;
case Token.STRING:
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.OBJECTLIT:
JSDocInfo info = n.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
if (lentObjectLiterals == null) {
lentObjectLiterals = Lists.newArrayList();
}
lentObjectLiterals.add(n);
} else {
defineObjectLiteral(n);
}
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void defineObjectLiteral(Node objectLit) {
// Handle the @lends annotation.
JSType type = null;
JSDocInfo info = objectLit.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
String lendsName = info.getLendsName();
Var lendsVar = scope.getVar(lendsName);
if (lendsVar == null) {
compiler.report(
JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName));
} else {
type = lendsVar.getType();
if (type == null) {
type = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) {
compiler.report(
JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT,
lendsName, type.toString()));
type = null;
} else {
objectLit.setJSType(type);
}
}
}
info = NodeUtil.getBestJSDocInfo(objectLit);
Node lValue = NodeUtil.getBestLValue(objectLit);
String lValueName = NodeUtil.getBestLValueName(lValue);
boolean createdEnumType = false;
if (info != null && info.hasEnumParameterType()) {
type = createEnumTypeFromNodes(objectLit, lValueName, info, lValue);
createdEnumType = true;
}
if (type == null) {
type = typeRegistry.createAnonymousObjectType();
}
setDeferredType(objectLit, type);
// If this is an enum, the properties were already taken care of above.
processObjectLitProperties(
objectLit, ObjectType.cast(objectLit.getJSType()), !createdEnumType);
}
/**
* Process an object literal and all the types on it.
* @param objLit The OBJECTLIT node.
* @param objLitType The type of the OBJECTLIT node. This might be a named
* type, because of the lends annotation.
* @param declareOnOwner If true, declare properties
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> on the objLitType as
* well. If false, the caller should take care of this.
*/
void processObjectLitProperties(
Node objLit, ObjectType objLitType,
boolean declareOnOwner) {
for (Node keyNode = objLit.getFirstChild(); keyNode != null;
keyNode = keyNode.getNext()) {
Node value = keyNode.getFirstChild();
String memberName = NodeUtil.getObjectLitKeyName(keyNode);
JSDocInfo info = keyNode.getJSDocInfo();
JSType valueType =
getDeclaredType(keyNode.getSourceFileName(), info, keyNode, value);
JSType keyType = objLitType.isEnumType() ?
objLitType.toMaybeEnumType().getElementsType() :
NodeUtil.getObjectLitKeyTypeFromValueType(keyNode, valueType);
// Try to declare this property in the current scope if it
// has an authoritative name.
String qualifiedName = NodeUtil.getBestLValueName(keyNode);
if (qualifiedName != null) {
boolean inferred = keyType == null;
defineSlot(keyNode, objLit, qualifiedName, keyType, inferred);
} else if (keyType != null) {
setDeferredType(keyNode, keyType);
}
if (keyType != null && objLitType != null && declareOnOwner) {
// Declare this property on its object literal.
boolean isExtern = keyNode.isFromExterns();
objLitType.defineDeclaredProperty(memberName, keyType, keyNode);
}
}
}
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
private JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode =
node.isGetProp() ? node.getFirstChild() :
NodeUtil.isObjectLitKey(node, node.getParent()) ? node.getParent() :
null;
if (info != null) {
if (info.hasType()) {
jsType = info.getType().evaluate(scope, typeRegistry);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
jsType = createFunctionTypeFromNodes(
null, fnName, info, node);
}
}
return jsType;
}
/**
* Asserts that it's OK to define this node's name.
* The node should have a source name and be of the specified type.
*/
void assertDefinitionNode(Node n, int type) {
Preconditions.checkState(sourceName != null);
Preconditions.checkState(n.getType() == type);
}
/**
* Defines a catch parameter.
*/
void defineCatch(Node n, Node parent) {
assertDefinitionNode(n, Token.CATCH);
Node catchName = n.getFirstChild();
defineSlot(catchName, n, null);
}
/**
* Defines a VAR initialization.
*/
void defineVar(Node n, Node parent) {
assert
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>DefinitionNode(n, Token.VAR);
JSDocInfo info = n.getJSDocInfo();
if (n.hasMoreThanOneChild()) {
if (info != null) {
// multiple children
compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF));
}
for (Node name : n.children()) {
defineName(name, n, parent, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, parent,
(info != null) ? info : name.getJSDocInfo());
}
}
/**
* Defines a function literal.
*/
void defineFunctionLiteral(Node n, Node parent) {
assertDefinitionNode(n, Token.FUNCTION);
// Determine the name and JSDocInfo and l-value for the function.
// Any of these may be null.
Node lValue = NodeUtil.getBestLValue(n);
JSDocInfo info = NodeUtil.getBestJSDocInfo(n);
String functionName = NodeUtil.getBestLValueName(lValue);
FunctionType functionType =
createFunctionTypeFromNodes(n, functionName, info, lValue);
// Assigning the function type to the function node
setDeferredType(n, functionType);
// Declare this symbol in the current scope iff it's a function
// declaration. Otherwise, the declaration will happen in other
// code paths.
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param name The {@link Token#NAME} node.
* @param var The parent of the {@code name} node, which must be a
* {@link Token#VAR} node.
* @param parent {@code var}'s parent.
* @param info the {@link JSDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, Node parent, JSDocInfo info) {
Node value = name.getFirstChild();
// variable's type
JSType type = getDeclaredType(sourceName, info, name, value);
if (type == null) {
// The variable's type will be inferred.
type = name.isFromExterns() ?
getNativeType(UNKNOWN_TYPE) : null;
}
defineSlot(name, var, type);
}
/**
* If a variable is assigned a function literal in the global scope,
* make that a declared type (even if there's no doc info).
* There's only one exception to this rule:
* if the return type is inferred, and we're in a local
* scope, we should assume the whole function is inferred.
*/
private boolean shouldUseFunctionLiteralType(
FunctionType type, JSDocInfo info, Node lValue) {
if (info != null) {
return true;
}
if (lValue != null &&
NodeUtil.isObjectLitKey(lValue, lValue.getParent())) {
return false;
}
return scope.isGlobal() ||
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS> !type.isReturnTypeInferred();
}
/**
* Creates a new function type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - A function literal that needs a type attached to it.
* - An assignment expression with function-type info in the JsDoc.
*
* All parameters are optional, and we will do the best we can to create
* a function type.
*
* This function will always create a function type, so only call it if
* you're sure that's what you want.
*
* @param rValue The function node.
* @param name the function's name
* @param info the {@link JSDocInfo} attached to the function definition
* @param lvalueNode The node where this function is being
* assigned. For example, {@code A.prototype.foo = ...} would be used to
* determine that this function is a method of A.prototype. May be
* null to indicate that this is not being assigned to a qualified name.
*/
private FunctionType createFunctionTypeFromNodes(
@Nullable Node rValue,
@Nullable String name,
@Nullable JSDocInfo info,
@Nullable Node lvalueNode) {
FunctionType functionType = null;
// Global ctor aliases should be registered with the type registry.
if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) {
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() != null &&
var.getType().isFunctionType()) {
FunctionType aliasedType = var.getType().toMaybeFunctionType();
if ((aliasedType.isConstructor() || aliasedType.isInterface()) &&
!aliasedType.isNativeObjectType()) {
functionType = aliasedType;
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, functionType.getInstanceType());
}
}
}
}
if (functionType == null) {
Node errorRoot = rValue == null ? lvalueNode : rValue;
boolean isFnLiteral =
rValue != null && rValue.isFunction();
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ?
rValue.getFirstChild().getNext() : null;
Node fnBlock = isFnLiteral ? parametersNode.getNext() : null;
if (info != null && info.hasType()) {
JSType type = info.getType().evaluate(scope, typeRegistry);
// Known to be not null since we have the FUNCTION token there.
type = type.restrictByNotNullOrUndefined();
if (type.isFunctionType()) {
functionType = type.toMaybeFunctionType();
functionType.setJSDocInfo(info);
}
}
if (functionType == null) {
// Find the type of any overridden function.
Node ownerNode = NodeUtil.getBestLValueOwner(lvalueNode);
String ownerName = NodeUtil.getBestLValueName(ownerNode);
Var ownerVar = null;
String propName = null;
ObjectType ownerType = null;
if (ownerName != null) {
ownerVar = scope.
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>.hasEnumParameterType());
EnumType enumType = null;
if (rValue != null && rValue.isQualifiedName()) {
// Handle an aliased enum.
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof EnumType) {
enumType = (EnumType) var.getType();
}
}
if (enumType == null) {
JSType elementsType =
info.getEnumParameterType().evaluate(scope, typeRegistry);
enumType = typeRegistry.createEnumType(name, rValue, elementsType);
if (rValue != null && rValue.isObjectLit()) {
// collect enum elements
Node key = rValue.getFirstChild();
while (key != null) {
String keyName = NodeUtil.getStringValue(key);
if (keyName == null) {
// GET and SET don't have a String value;
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else if (!codingConvention.isValidEnumKey(keyName)) {
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else {
enumType.defineElement(keyName, key);
}
key = key.getNext();
}
}
}
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, enumType.getElementsType());
}
return enumType;
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type or {@code null} if its type is inferred.
* @param name the defining node. It must be a {@link Token#NAME}.
* @param parent the {@code name}'s parent.
* @param type the variable's type. It may be {@code null}, in which case
* the variable's type will be inferred.
*/
private void defineSlot(Node name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualified names.
// Object literal keys will have to compute their names themselves.
if (n.isName()) {
Preconditions.checkArgument(
parent.isFunction() ||
parent.isVar() ||
parent.isParamList() ||
parent.isCatch());
} else {
Preconditions.checkArgument(
n.isGetProp() &&
(parent.isAssign() ||
parent.isExprResult()));
}
defineSlot(n,
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>");
// When we declare the function prototype implicitly, we
// want to make sure that the function and its prototype
// are declared at the same node. We also want to make sure
// that the if a symbol has both a Var and a JSType, they have
// the same node.
//
// This consistency is helpful to users of SymbolTable,
// because everything gets declared at the same place.
prototypeSlot.setNode(n);
String prototypeName = variableName + ".prototype";
// There are some rare cases where the prototype will already
// be declared. See TypedScopeCreatorTest#testBogusPrototypeInit.
// Fortunately, other warnings will complain if this happens.
Var prototypeVar = scopeToDeclareIn.getVar(prototypeName);
if (prototypeVar != null && prototypeVar.scope == scopeToDeclareIn) {
scopeToDeclareIn.undeclare(prototypeVar);
}
scopeToDeclareIn.declare(prototypeName,
n, prototypeSlot.getType(), input,
/* declared iff there's an explicit supertype */
superClassCtor == null ||
superClassCtor.getInstanceType().equals(
getNativeType(OBJECT_TYPE)));
// Make sure the variable is initialized to something if
// it constructs itself.
if (newVar.getInitialValue() == null &&
!isExtern &&
// We want to make sure that when we declare a new instance
// type (with @constructor) that there's actually a ctor for it.
// This doesn't apply to structural constructors
// (like function(new:Array). Checking the constructed
// type against the variable name is a sufficient check for
// this.
variableName.equals(
fnType.getInstanceType().getReferenceName())) {
compiler.report(
JSError.make(sourceName, n,
fnType.isConstructor() ?
CTOR_INITIALIZER : IFACE_INITIALIZER,
variableName));
}
}
}
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(GLOBAL_THIS);
if (inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
getNativeType(JSTypeNative.NO_TYPE) :
type,
n);
} else {
globalThis.defineDeclaredProperty(variableName, type, n);
}
}
if (isGlobalVar && "Window".equals(variableName)
&& type != null
&& type.isFunctionType()
&& type.isConstructor()) {
FunctionType globalThisCtor =
typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor();
globalThisCtor.getInstanceType().clearCachedValues();
globalThisCtor.getPrototype().clearCachedValues();
globalThisCtor
.setPrototypeBasedOn((type.toMaybeFunctionType()).getInstanceType());
}
}
/**
* Check if the given node is a property of a name in the global scope.
*/
private boolean isQnameRootedInGlobalScope(Node n) {
Scope scope = getQnameRootScope(n);
return scope != null && scope.isGlobal();
}
/**
* Return the scope for the name of the given node.
*/
private Scope getQnameRootScope(Node n) {
Node root = NodeUtil.getRootOfQualifiedName(
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>oxious.
*
* The problem is that there are two (equally valid) coding styles:
*
* (function() {
* /* The authoritative definition of goog.bar. /
* goog.bar = function() {};
* })();
*
* function f() {
* goog.bar();
* /* Reset goog.bar to a no-op. /
* goog.bar = function() {};
* }
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
// Check if this is in a conditional block.
// Functions assigned in conditional blocks are inferred.
for (Node current = n.getParent();
!(current.isScript() || current.isFunction());
current = current.getParent()) {
if (NodeUtil.isControlStructure(current)) {
return true;
}
}
// Check if this is assigned in an inner scope.
// Functions assigned in inner scopes are inferred.
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents == null ||
!contents.getEscapedQualifiedNames().contains(qName)) {
return false;
}
}
}
return inferred;
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub declarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.
Closure, 20
<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB>
replacement.addChildToBack(fixedIfCondition);
}
reportCodeChange();
}
}
}
private Node tryFoldSimpleFunctionCall(Node n) {
Preconditions.checkState(n.isCall());
Node callTarget = n.getFirstChild();
if (callTarget != null && callTarget.isName() &&
callTarget.getString().equals("String")) {
// Fold String(a) to '' + (a) on immutable literals,
// which allows further optimizations
//
// We can't do this in the general case, because String(a) has
// slightly different semantics than '' + (a). See
// http://code.google.com/p/closure-compiler/issues/detail?id=759
Node value = callTarget.getNext();
<CHANGES>
if (value != null) {
<CHANGEE>
Node addition = IR.add(
IR.string("").srcref(callTarget),
value.detachFromParent());
n.getParent().replaceChild(n, addition);
reportCodeChange();
return addition;
}
}
return n;
}
private Node tryFoldImmediateCallToBoundFunction(Node n) {
// Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable
<FILEE>
<SCANS>Traversal t, Node n, Node parent) {
if (t.inGlobalScope()) {
return;
}
if (n.isReturn() && n.getFirstChild() != null) {
data.get(t.getScopeRoot()).recordNonEmptyReturn();
}
if (t.getScopeDepth() <= 2) {
// We only need to worry about escaped variables at depth 3.
// An variable escaped at depth 2 is, by definition, a global variable.
// We treat all global variables as escaped by default, so there's
// no reason to do this extra computation for them.
return;
}
if (n.isName() && NodeUtil.isLValue(n)) {
String name = n.getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordEscapedVarName(name);
}
}
} else if (n.isGetProp() && n.isUnscopedQualifiedName() &&
NodeUtil.isLValue(n)) {
String name = NodeUtil.getRootOfQualifiedName(n).getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode())
.recordEscapedQualifiedName(n.getQualifiedName());
}
}
}
}
}
private AstFunctionContents getFunctionAnalysisResults(@Nullable Node n) {
if (n == null) {
return null;
}
// Sometimes this will return null in things like
// NameReferenceGraphConstruction that build partial scopes.
return functionAnalysisResults.get(n);
}
}